<?php
/**
 * Gutenberg block registration.
 *
 * @package Rsmspark_Content_Widgets
 */

defined( 'ABSPATH' ) || exit;

/**
 * Rsmspark_Content_Widgets_Blocks
 *
 * Refactored in compat branch - Phase 1
 */
class Rsmspark_Content_Widgets_Blocks {

	const EDITOR_SCRIPT_HANDLE = 'rsmspark-content-widgets-blocks-editor';

	const BOOTSTRAP_SCRIPT_HANDLE = 'rsmspark-content-widgets-blocks-bootstrap';

	const DEBUG_SCRIPT_HANDLE = 'rsmspark-content-widgets-editor-debug';

	const BLOCK_CATEGORY = 'rsm-spark';

	const BLOCK_NAMESPACE = 'rsm-content-widgets';

	const LEGACY_BLOCK_NAMESPACE = 'rsmspark-cw-compat';

	const BRAND_ORANGE = '#ff6b35';

	/**
	 * Pending block definitions.
	 *
	 * @var array<string, array<string, mixed>>
	 */
	private static $registry = array();

	/**
	 * Whether widget bootstrap registered block definitions for this request.
	 *
	 * @return bool
	 */
	public static function has_block_definitions() {
		return ! empty( self::$registry );
	}

	/**
	 * Unvalidated block attributes from the block-renderer REST body (per request).
	 *
	 * @var array<string, array<string, mixed>>
	 */
	private static $raw_block_renderer_attributes = array();

	/**
	 * @return void
	 */
	public static function init() {
		Rsmspark_Content_Widgets_Render::init_embed_support();

		add_filter( 'rsmspark_content_widgets_should_render', array( __CLASS__, 'allow_editor_preview' ), 10, 2 );
		add_filter( 'rest_pre_dispatch', array( __CLASS__, 'capture_raw_block_renderer_attributes' ), 5, 3 );
		add_action( 'enqueue_block_editor_assets', array( __CLASS__, 'enqueue_editor_bootstrap' ), 5 );
		add_action( 'enqueue_block_editor_assets', array( __CLASS__, 'enqueue_editor_assets' ) );
		add_action( 'enqueue_block_editor_assets', array( __CLASS__, 'enqueue_embed_chrome_editor' ), 25 );
		add_action( 'enqueue_block_editor_assets', array( __CLASS__, 'maybe_log_editor_debug' ), 100 );
		add_action( 'admin_print_footer_scripts', array( __CLASS__, 'ensure_editor_script_output' ), 9999 );
		add_action( 'admin_notices', array( __CLASS__, 'maybe_render_editor_debug_notice' ) );
		add_action( 'init', array( __CLASS__, 'register_all' ), 20 );
		add_action( 'init', array( __CLASS__, 'register_all' ), 99 );
		add_action( 'init', array( __CLASS__, 'init_block_layout_supports' ), 12 );
		add_filter( 'register_block_type_args', array( __CLASS__, 'suppress_duplicate_sowb_inserter' ), 20, 2 );
	}

	/**
	 * True while a block render_callback is executing (front or editor).
	 *
	 * @var bool
	 */
	private static $in_block_render = false;

	/**
	 * @return bool
	 */
	public static function is_in_block_render() {
		return self::$in_block_render;
	}

	/**
	 * RSF layout band + spacing (works with or without RSM Focus active).
	 *
	 * @return void
	 */
	public static function init_block_layout_supports() {
		if ( ! Rsmspark_Content_Widgets_Settings::block_editor_ui_enabled() ) {
			return;
		}

		if ( ! defined( 'RSMSPARK_RSF_SHARED_DIR' ) ) {
			define( 'RSMSPARK_RSF_SHARED_DIR', RSMSPARK_CONTENT_WIDGETS_PATH . 'rsm-shared/' );
		}

		if ( ! defined( 'RSMSPARK_RSF_SHARED_URL' ) ) {
			define( 'RSMSPARK_RSF_SHARED_URL', RSMSPARK_CONTENT_WIDGETS_URL . 'rsm-shared/' );
		}

		$settings_file = RSMSPARK_CONTENT_WIDGETS_PATH . 'rsm-shared/includes/class-rsmspark-rsf-settings.php';
		$supports_file = RSMSPARK_CONTENT_WIDGETS_PATH . 'rsm-shared/includes/class-rsmspark-block-supports.php';

		if ( is_readable( $settings_file ) && ! class_exists( 'RSMSPARK_RSF_Settings', false ) ) {
			require_once $settings_file;
		}

		if ( is_readable( $supports_file ) && ! class_exists( 'Rsmspark_Block_Supports', false ) ) {
			require_once $supports_file;
		}

		if ( class_exists( 'Rsmspark_Block_Supports', false ) ) {
			Rsmspark_Block_Supports::init();
		}
	}

	/**
	 * Load standalone embed inspector only when RSF block controls are not active.
	 *
	 * @return void
	 */
	public static function enqueue_embed_chrome_editor(): void {
		if ( ! is_admin() || ! Rsmspark_Content_Widgets_Settings::block_editor_ui_enabled() ) {
			return;
		}

		if ( ! class_exists( 'Rsmspark_Embed_Chrome', false ) ) {
			return;
		}

		if ( wp_script_is( 'rsmspark-rsf-block-controls', 'enqueued' ) ) {
			return;
		}

		Rsmspark_Embed_Chrome::enqueue_editor();
	}

	/**
	 * @param array<string, array<string, mixed>> $attributes Block attributes.
	 * @return array<string, array<string, mixed>>
	 */
	private static function merge_embed_block_attributes( array $attributes ): array {
		if ( ! class_exists( 'Rsmspark_Embed_Chrome', false ) ) {
			return $attributes;
		}

		return array_merge( $attributes, Rsmspark_Embed_Chrome::attribute_schema() );
	}

	/**
	 * @return void
	 */
	public static function ensure_block_layout_editor_assets() {
		self::init_block_layout_supports();

		if ( ! class_exists( 'Rsmspark_Block_Supports', false ) ) {
			return;
		}

		if ( ! wp_script_is( 'rsmspark-rsf-block-controls', 'enqueued' ) && method_exists( 'Rsmspark_Block_Supports', 'enqueue_editor' ) ) {
			Rsmspark_Block_Supports::enqueue_editor();
		}
	}

	/**
	 * Hide SOWB widget blocks from the inserter when native RSM blocks exist.
	 *
	 * Keeps registration so existing sowb/* block content still loads in the editor.
	 *
	 * @param array<string, mixed> $args Block type registration args.
	 * @param string               $name Block name.
	 * @return array<string, mixed>
	 */
	public static function suppress_duplicate_sowb_inserter( $args, $name ) {
		if ( ! Rsmspark_Content_Widgets_Settings::block_editor_ui_enabled() || ! class_exists( 'Rsmspark_Content_Widgets_SiteOrigin', false ) ) {
			return $args;
		}

		$hide = Rsmspark_Content_Widgets_SiteOrigin::sowb_gutenberg_blocks_with_native_duplicates();
		if ( ! in_array( $name, $hide, true ) ) {
			return $args;
		}

		if ( ! isset( $args['supports'] ) || ! is_array( $args['supports'] ) ) {
			$args['supports'] = array();
		}

		$args['supports']['inserter'] = false;

		return $args;
	}

		/**
		 * Allow enabled free widgets to render in the block editor preview.
		 *
		 * @param bool   $should      Whether the widget may render.
		 * @param string $registry_id Widget registry id.
		 * @return bool
		 */
	public static function allow_editor_preview( $should, $registry_id ) {
		if ( $should || ! self::is_editor_preview_request() ) {
			return $should;
		}

		$id = sanitize_key( (string) $registry_id );
		if ( ! Rsmspark_Content_Widgets_Settings::is_widget_enabled( $id ) ) {
			return false;
		}

		return Rsmspark_Content_Widgets_Settings::is_widget_enabled( $id );
	}

	/**
	 * True when the block editor is requesting PHP render output via REST.
	 *
	 * @return bool
	 */
	public static function is_editor_preview_request() {
		if ( ! defined( 'REST_REQUEST' ) || ! REST_REQUEST ) {
			return false;
		}

		$route = '';
		if ( isset( $GLOBALS['wp']->query_vars['rest_route'] ) ) {
			$route = (string) $GLOBALS['wp']->query_vars['rest_route'];
		} elseif ( isset( $_SERVER['REQUEST_URI'] ) ) {
			$route = sanitize_text_field( wp_unslash( (string) $_SERVER['REQUEST_URI'] ) );
		}

		return false !== strpos( $route, '/wp/v2/block-renderer/' . self::BLOCK_NAMESPACE . '/' )
			|| false !== strpos( $route, '/wp/v2/block-renderer/' . self::LEGACY_BLOCK_NAMESPACE . '/' );
	}

	/**
	 * Block library hover preview only (not the post canvas).
	 *
	 * @return bool
	 */
	public static function is_inserter_library_preview() {
		if ( ! self::is_editor_preview_request() ) {
			return false;
		}

		// phpcs:ignore WordPress.Security.NonceVerification.Recommended
		return ! empty( $_GET['rsm_inserter_preview'] );
	}

	/**
	 * Demo asset URL for inserter hover previews.
	 *
	 * @param string $slug Asset basename without extension (e.g. inserter-demo-before).
	 * @return string
	 */
	public static function inserter_demo_asset_url( $slug ) {
		$slug = sanitize_key( (string) $slug );
		if ( '' === $slug ) {
			return '';
		}

		return RSMSPARK_CONTENT_WIDGETS_URL . 'assets/images/' . $slug . '.svg';
	}

	/**
	 * @return void
	 */
	public static function enqueue_editor_bootstrap() {
		if ( ! Rsmspark_Content_Widgets_Settings::block_editor_ui_enabled() ) {
			return;
		}

		self::ensure_widget_registry();
		if ( empty( self::$registry ) ) {
			return;
		}

		self::register_editor_script();

		if ( wp_script_is( self::BOOTSTRAP_SCRIPT_HANDLE, 'registered' ) ) {
			wp_enqueue_script( self::BOOTSTRAP_SCRIPT_HANDLE );
		}
	}

	/**
	 * @return void
	 */
	public static function enqueue_editor_assets() {
		if ( ! Rsmspark_Content_Widgets_Settings::block_editor_ui_enabled() ) {
			return;
		}

		self::ensure_widget_registry();

		if ( empty( self::$registry ) ) {
			return;
		}

		self::register_block_asset_handles();

		self::ensure_block_layout_editor_assets();
		self::enqueue_editor_preview_assets();
		if ( class_exists( 'Rsmspark_Rich_Text_Assets', false ) ) {
			Rsmspark_Rich_Text_Assets::register_admin();
			wp_enqueue_style( Rsmspark_Rich_Text_Assets::ADMIN_STYLE );
		}
		if ( class_exists( 'Rsmspark_Rich_Text_TinyMCE', false ) ) {
			Rsmspark_Rich_Text_TinyMCE::enqueue_gutenberg_editor( 'post.php' );
		}
		if ( class_exists( 'Rsmspark_Rich_Text_Editor_UI', false ) ) {
			Rsmspark_Rich_Text_Editor_UI::enqueue_block_editor_media_bridge();
		}
		if ( class_exists( 'Rsmspark_Content_Widgets_Media_Picker', false ) ) {
			Rsmspark_Content_Widgets_Media_Picker::enqueue_admin( 'post.php' );
		}

		if ( class_exists( 'Rsmspark_Block_Editor_Branding', false ) ) {
			Rsmspark_Block_Editor_Branding::enqueue_block_editor_stack(
				self::EDITOR_SCRIPT_HANDLE,
				array( 'rsmspark-content-widgets-blocks-editor' )
			);
		} else {
			wp_enqueue_script( self::EDITOR_SCRIPT_HANDLE );
			wp_enqueue_style( 'rsmspark-content-widgets-blocks-editor' );
		}

		if ( class_exists( 'Rsmspark_Content_Widgets_Assets', false ) ) {
			Rsmspark_Content_Widgets_Assets::enqueue_export_json_ui_assets();
		}

		if (
			class_exists( 'RSMSPARK_RSF_Svg_Icon_Block', false )
			&& Rsmspark_Content_Widgets_Settings::is_widget_enabled( 'svg_icon' )
		) {
			if ( ! wp_style_is( RSMSPARK_RSF_Svg_Icon_Block::EDITOR_STYLE_HANDLE, 'registered' ) ) {
				Rsmspark_Content_Widgets_Widget_Svg_Icon::register_assets();
			}
			wp_enqueue_style( RSMSPARK_RSF_Svg_Icon_Block::EDITOR_STYLE_HANDLE );
			if ( wp_style_is( RSMSPARK_RSF_Svg_Icon_Block::STYLE_HANDLE, 'registered' ) ) {
				wp_enqueue_style( RSMSPARK_RSF_Svg_Icon_Block::STYLE_HANDLE );
			}
			if ( wp_script_is( RSMSPARK_RSF_Svg_Icon_Block::EDITOR_SCRIPT_HANDLE, 'registered' ) ) {
				wp_enqueue_script( RSMSPARK_RSF_Svg_Icon_Block::EDITOR_SCRIPT_HANDLE );
			}
		}
	}

	/**
	 * Last-chance print when WordPress queued blocks-editor.js but skipped the tag.
	 *
	 * @return void
	 */
	public static function ensure_editor_script_output() {
		if ( ! Rsmspark_Content_Widgets_Settings::block_editor_ui_enabled() ) {
			return;
		}

		if ( ! function_exists( 'get_current_screen' ) ) {
			return;
		}

		$screen = get_current_screen();
		if ( ! $screen || ! method_exists( $screen, 'is_block_editor' ) || ! $screen->is_block_editor() ) {
			return;
		}

		if ( wp_script_is( self::EDITOR_SCRIPT_HANDLE, 'done' ) ) {
			return;
		}

		if ( ! wp_script_is( self::EDITOR_SCRIPT_HANDLE, 'registered' ) ) {
			self::register_editor_script();
		}

		wp_enqueue_script( self::EDITOR_SCRIPT_HANDLE );

		if ( wp_script_is( self::EDITOR_SCRIPT_HANDLE, 'done' ) ) {
			return;
		}

		wp_scripts()->do_item( self::EDITOR_SCRIPT_HANDLE );
	}

	/**
	 * Front-end widget CSS/JS for in-editor ServerSideRender preview fidelity.
	 *
	 * @return void
	 */
	public static function enqueue_editor_preview_assets() {
		wp_enqueue_style( 'dashicons' );

		wp_enqueue_style(
			'rsmspark-content-widgets',
			RSMSPARK_CONTENT_WIDGETS_URL . 'assets/css/rsm-content-widgets.css',
			array(),
			RSMSPARK_CONTENT_WIDGETS_VERSION
		);

		if ( class_exists( 'Rsmspark_Rich_Text_Assets', false ) ) {
			Rsmspark_Rich_Text_Assets::enqueue_front();
		}

		wp_enqueue_script(
			'rsmspark-content-widgets',
			RSMSPARK_CONTENT_WIDGETS_URL . 'assets/js/rsm-content-widgets.js',
			array(),
			RSMSPARK_CONTENT_WIDGETS_VERSION,
			true
		);

		wp_enqueue_style(
			'rsmspark-content-widgets-features-fontawesome',
			RSMSPARK_CONTENT_WIDGETS_URL . 'assets/vendor/fontawesome/css/all.min.css',
			array(),
			'6.5.2'
		);

		wp_enqueue_style(
			'rsmspark-content-widgets-features',
			RSMSPARK_CONTENT_WIDGETS_URL . 'assets/css/rsm-features.css',
			array( 'rsmspark-content-widgets-features-fontawesome', 'dashicons', 'rsmspark-content-widgets' ),
			RSMSPARK_CONTENT_WIDGETS_VERSION
		);

		wp_enqueue_script(
			'rsmspark-content-widgets-cw-modal',
			RSMSPARK_CONTENT_WIDGETS_URL . 'assets/js/rsm-cw-modal.js',
			array(),
			RSMSPARK_CONTENT_WIDGETS_VERSION,
			true
		);

		wp_enqueue_script(
			'rsmspark-content-widgets-features',
			RSMSPARK_CONTENT_WIDGETS_URL . 'assets/js/rsm-features.js',
			array( 'jquery', 'rsmspark-content-widgets-cw-modal' ),
			RSMSPARK_CONTENT_WIDGETS_VERSION,
			true
		);

		wp_enqueue_style(
			'rsmspark-content-widgets-footer-fontawesome',
			RSMSPARK_CONTENT_WIDGETS_URL . 'assets/vendor/fontawesome/css/all.min.css',
			array(),
			'6.5.2'
		);

		wp_enqueue_style(
			'rsmspark-content-widgets-footer',
			RSMSPARK_CONTENT_WIDGETS_URL . 'assets/css/rsm-footer.css',
			array( 'rsmspark-content-widgets-footer-fontawesome', 'dashicons', 'rsmspark-content-widgets' ),
			RSMSPARK_CONTENT_WIDGETS_VERSION
		);

		wp_enqueue_style(
			'rsmspark-buttons',
			RSMSPARK_CONTENT_WIDGETS_URL . 'assets/css/rsm-buttons.css',
			array( 'dashicons' ),
			RSMSPARK_CONTENT_WIDGETS_VERSION
		);
	}

	/**
	 * Queue a dynamic block for registration.
	 *
	 * @param string               $name Block name without namespace (e.g. hero).
	 * @param array<string, mixed> $args Block registration args.
	 * @return void
	 */
	public static function add( $name, array $args ) {
		$name = sanitize_key( (string) $name );
		if ( '' === $name ) {
			return;
		}

		if ( empty( $args['category'] ) ) {
			$args['category'] = self::BLOCK_CATEGORY;
		}

		self::$registry[ $name ] = $args;
	}

	/**
	 * Ensure widget classes registered block definitions before editor/block init.
	 *
	 * @return void
	 */
	private static function ensure_widget_registry() {
		if ( ! empty( self::$registry ) ) {
			return;
		}

		if ( class_exists( 'Rsmspark_Content_Widgets_Bootstrap', false ) ) {
			Rsmspark_Content_Widgets_Bootstrap::register_all();
		}
	}

	/**
	 * @return bool
	 */
	private static function editor_inline_config_attached() {
		foreach ( array( self::BOOTSTRAP_SCRIPT_HANDLE, self::EDITOR_SCRIPT_HANDLE ) as $handle ) {
			if ( ! wp_script_is( $handle, 'registered' ) ) {
				continue;
			}

			$data = wp_scripts()->get_data( $handle, 'data' );
			if ( ! is_string( $data ) ) {
				continue;
			}

			if (
				false !== strpos( $data, 'rsmsparkContentWidgetsBlocks' )
				|| false !== strpos( $data, 'rsmContentWidgetsBlocks' )
			) {
				return true;
			}
		}

		return false;
	}

	/**
	 * Script handles added only when already registered (avoids blocking enqueue).
	 *
	 * @return array<int, string>
	 */
	private static function editor_script_dependencies() {
		$deps = array(
			'wp-blocks',
			'wp-element',
			'wp-block-editor',
			'wp-components',
			'wp-i18n',
			'wp-api-fetch',
			'wp-server-side-render',
			'wp-hooks',
			'wp-data',
		);

		// Bootstrap registers dynamic blocks first; full editor replaces edit UI.
		// Do not list rich-text, jquery, media picker, or preview handles here —
		// they may be wp_enqueue_script()'d but not printed in the block editor,
		// which causes WordPress to skip blocks-editor.js entirely.
		if ( wp_script_is( self::BOOTSTRAP_SCRIPT_HANDLE, 'registered' ) ) {
			$deps[] = self::BOOTSTRAP_SCRIPT_HANDLE;
		}

		return $deps;
	}

	/**
	 * @return array<int, string>
	 */
	private static function bootstrap_script_dependencies() {
		return array(
			'wp-blocks',
			'wp-element',
			'wp-block-editor',
			'wp-server-side-render',
		);
	}

	/**
	 * @param array<string, mixed> $editor_config Editor config payload.
	 * @param array<string, mixed> $attribute_schemas Block attribute schemas.
	 * @return void
	 */
	private static function attach_editor_inline_config( array $editor_config, array $attribute_schemas ) {
		static $attached = false;
		if ( $attached ) {
			return;
		}
		$attached = true;

		foreach ( array( self::BOOTSTRAP_SCRIPT_HANDLE, self::EDITOR_SCRIPT_HANDLE ) as $handle ) {
			if ( ! wp_script_is( $handle, 'registered' ) ) {
				continue;
			}

			wp_localize_script( $handle, 'rsmsparkContentWidgetsBlocks', $editor_config );
			wp_localize_script( $handle, 'rsmsparkContentWidgetsBlockAttributes', $attribute_schemas );
		}
	}

	/**
	 * @return void
	 */
	public static function register_editor_script() {
		self::ensure_widget_registry();

		self::init_block_layout_supports();
		if ( class_exists( 'Rsmspark_Block_Supports', false ) && method_exists( 'Rsmspark_Block_Supports', 'register_editor_assets' ) ) {
			Rsmspark_Block_Supports::register_editor_assets();
		}

		if ( class_exists( 'Rsmspark_Content_Widgets_Media_Picker', false ) ) {
			Rsmspark_Content_Widgets_Media_Picker::register_script();
		}

		if ( class_exists( 'Rsmspark_Content_Widgets_Assets', false ) ) {
			Rsmspark_Content_Widgets_Assets::register_export_json_ui_assets();
		}

		if ( class_exists( 'Rsmspark_Rich_Text_Assets', false ) ) {
			Rsmspark_Rich_Text_Assets::register_admin();
		}

		self::register_block_asset_handles();

		if ( ! wp_script_is( self::BOOTSTRAP_SCRIPT_HANDLE, 'registered' ) ) {
			wp_register_script(
				self::BOOTSTRAP_SCRIPT_HANDLE,
				RSMSPARK_CONTENT_WIDGETS_URL . 'assets/js/blocks-editor-bootstrap.js',
				self::bootstrap_script_dependencies(),
				RSMSPARK_CONTENT_WIDGETS_VERSION,
				true
			);
		}

		if ( ! wp_script_is( self::EDITOR_SCRIPT_HANDLE, 'registered' ) ) {
			wp_register_script(
				self::EDITOR_SCRIPT_HANDLE,
				RSMSPARK_CONTENT_WIDGETS_URL . 'assets/js/blocks-editor.js',
				self::editor_script_dependencies(),
				RSMSPARK_CONTENT_WIDGETS_VERSION,
				true
			);
		}

		$editor_style_deps = array();
		if ( wp_style_is( 'rsmspark-rich-text-admin', 'registered' ) ) {
			$editor_style_deps[] = 'rsmspark-rich-text-admin';
		}

		if ( wp_style_is( 'rsmspark-content-widgets-blocks-editor', 'registered' ) ) {
			wp_deregister_style( 'rsmspark-content-widgets-blocks-editor' );
		}

		wp_register_style(
			'rsmspark-content-widgets-blocks-editor',
			RSMSPARK_CONTENT_WIDGETS_URL . 'assets/css/blocks-editor.css',
			$editor_style_deps,
			RSMSPARK_CONTENT_WIDGETS_VERSION
		);

		$blocks_meta       = array();
		$attribute_schemas = array();
		foreach ( self::$registry as $name => $args ) {
			$registry_id = Rsmspark_Content_Widgets_Registry::registry_id_from_block_slug( $name );
			if ( ! Rsmspark_Content_Widgets_Settings::is_widget_enabled( $registry_id ) ) {
				continue;
			}

			$block_name = self::BLOCK_NAMESPACE . '/' . $name;
			$editor_script   = (string) ( $args['editor_script'] ?? '' );
			$blocks_meta[] = array(
				'name'            => $block_name,
				'legacy_name'     => self::LEGACY_BLOCK_NAMESPACE . '/' . $name,
				'title'           => (string) ( $args['title'] ?? $name ),
				'description'     => (string) ( $args['description'] ?? '' ),
				'category'        => (string) ( $args['category'] ?? self::BLOCK_CATEGORY ),
				'icon'            => (string) ( $args['icon'] ?? 'screenoptions' ),
				'keywords'        => is_array( $args['keywords'] ?? null ) ? $args['keywords'] : array(),
				'supports'        => is_array( $args['supports'] ?? null ) ? $args['supports'] : array( 'html' => false ),
				'example'         => self::build_block_example( $name, $args ),
				'external_editor' => '' !== $editor_script && self::EDITOR_SCRIPT_HANDLE !== $editor_script,
			);
			$attribute_schemas[ $block_name ] = self::merge_embed_block_attributes(
				is_array( $args['attributes'] ?? null ) ? $args['attributes'] : array()
			);
			$attribute_schemas[ self::LEGACY_BLOCK_NAMESPACE . '/' . $name ] = $attribute_schemas[ $block_name ];
		}

		if ( ! class_exists( 'Rsmspark_Content_Widgets_Hero_Presets', false ) ) {
			require_once RSMSPARK_CONTENT_WIDGETS_PATH . 'includes/class-rsmspark-content-widgets-hero-presets.php';
		}
		if ( ! class_exists( 'Rsmspark_Content_Widgets_Footer_Visibility', false ) ) {
			require_once RSMSPARK_CONTENT_WIDGETS_PATH . 'includes/class-rsmspark-content-widgets-footer-visibility.php';
		}

		$editor_config = array(
			'blocks'            => $blocks_meta,
			'hideSowbBlocks'    => class_exists( 'Rsmspark_Content_Widgets_SiteOrigin', false )
				? Rsmspark_Content_Widgets_SiteOrigin::sowb_gutenberg_blocks_with_native_duplicates()
				: array(),
			'experimentalBlocks' => Rsmspark_Content_Widgets_Settings::experimental_blocks_enabled(),
			'previewDebounceMs' => 450,
			'useRsmMedia'       => class_exists( 'Rsmspark_Content_Widgets_Media_Picker', false )
				? Rsmspark_Content_Widgets_Media_Picker::should_use()
				: false,
			'version'           => RSMSPARK_CONTENT_WIDGETS_VERSION,
			'scriptUrl'         => RSMSPARK_CONTENT_WIDGETS_URL . 'assets/js/blocks-editor.js',
			'testimonialPublishedCount' => class_exists( 'Rsmspark_Testimonials_Library', false )
				? Rsmspark_Testimonials_Library::published_count()
				: 0,
			'heroPresets'       => Rsmspark_Content_Widgets_Hero_Presets::choices_for_editor(),
			'heroExportPlugin'  => Rsmspark_Content_Widgets_Hero_Presets::EXPORT_PLUGIN,
			'widgetJson'        => array(
				'ajaxUrl' => admin_url( 'admin-ajax.php' ),
				'nonce'   => wp_create_nonce( 'rsmspark_cw_widget_json' ),
			),
			'footerPageTypes'   => Rsmspark_Content_Widgets_Footer_Visibility::page_type_options(),
			'footerPostTypes'   => Rsmspark_Content_Widgets_Footer_Visibility::post_type_options(),
			'i18n'              => array(
				'content'      => __( 'Content', 'rsm-content-widgets' ),
				'footerCopyright'      => __( 'Copyright', 'rsm-content-widgets' ),
				'footerBrand'          => __( 'Brand', 'rsm-content-widgets' ),
				'footerBrandName'      => __( 'Brand name', 'rsm-content-widgets' ),
				'footerBrandDescription' => __( 'Brand description', 'rsm-content-widgets' ),
				'footerEditorNote'     => __( 'Appearance → Widgets uses this Gutenberg block (not the SiteOrigin widget). Set Region & visibility below, then Save — rules apply on the front end. Use SiteOrigin Page Builder for full column/link editing.', 'rsm-content-widgets' ),
				'footerColumns'        => __( 'Columns', 'rsm-content-widgets' ),
				'footerColumn'         => __( 'Column', 'rsm-content-widgets' ),
				'footerShowOn'         => __( 'Show on page types', 'rsm-content-widgets' ),
				'footerShowOnHelp'     => __( 'Leave all off to show everywhere (unless hidden below).', 'rsm-content-widgets' ),
				'footerHideOn'         => __( 'Hide on page types', 'rsm-content-widgets' ),
				'footerHideOnHelp'     => __( 'Hide this column when the current page matches any selected type.', 'rsm-content-widgets' ),
				'footerRegionVisibility' => __( 'Region & visibility', 'rsm-content-widgets' ),
				'footerRegion'         => __( 'Region slug', 'rsm-content-widgets' ),
				'footerRegionHelp'     => __( 'Optional label for admin reference (e.g. default, shop). Visibility rules below control where this footer appears.', 'rsm-content-widgets' ),
				'footerInstanceShowOn' => __( 'Show footer on page types', 'rsm-content-widgets' ),
				'footerInstanceHideOn' => __( 'Hide footer on page types', 'rsm-content-widgets' ),
				'footerShowOnPostTypes' => __( 'Show footer on post types', 'rsm-content-widgets' ),
				'footerShowOnPostTypesHelp' => __( 'Applies on single posts/pages of the selected types.', 'rsm-content-widgets' ),
				'footerHideOnPostTypes' => __( 'Hide footer on post types', 'rsm-content-widgets' ),
				'footerPlacement'      => __( 'Placement', 'rsm-content-widgets' ),
				'footerPlacementStatic' => __( 'Static (default)', 'rsm-content-widgets' ),
				'footerPlacementSticky' => __( 'Sticky bar (fixed to bottom)', 'rsm-content-widgets' ),
				'footerRevealMode'     => __( 'Sticky reveal', 'rsm-content-widgets' ),
				'footerRevealAlways'   => __( 'Always visible', 'rsm-content-widgets' ),
				'footerRevealScrollUp' => __( 'Reveal on scroll up (hide on scroll down)', 'rsm-content-widgets' ),
				'footerRevealScrollDown' => __( 'Hide on scroll up (reveal on scroll down)', 'rsm-content-widgets' ),
				'footerStickyCompact'  => __( 'Compact sticky bar', 'rsm-content-widgets' ),
				'footerBackToTop'      => __( 'Back to top button', 'rsm-content-widgets' ),
				'blockSettingsCanvasHelp' => __( 'Settings below also appear in the block sidebar when it is open.', 'rsm-content-widgets' ),
				'blockSettingsInSidebar'  => __( 'Block settings are open in the sidebar. Expand below to edit them here too.', 'rsm-content-widgets' ),
				'showBlockSettings'       => __( 'Show block settings', 'rsm-content-widgets' ),
				'showBlockSettingsJson'   => __( 'Show import/export & settings', 'rsm-content-widgets' ),
				'hideBlockSettings'       => __( 'Hide block settings', 'rsm-content-widgets' ),
				'hideBlockSettingsJson'   => __( 'Hide import/export & settings', 'rsm-content-widgets' ),
				'blockConfigJson'         => __( 'Import / export JSON', 'rsm-content-widgets' ),
				'blockConfigJsonHelp'     => __( 'Export or import this block as JSON. Use Show import/export & settings above when you need it.', 'rsm-content-widgets' ),
				'blockConfigExport'       => __( 'Export JSON', 'rsm-content-widgets' ),
				'blockConfigImport'       => __( 'Import JSON', 'rsm-content-widgets' ),
				'blockConfigImportApply'  => __( 'Apply import', 'rsm-content-widgets' ),
				'blockConfigImportConfirm' => __( 'Apply this JSON import? Current block settings will be overwritten.', 'rsm-content-widgets' ),
				'blockConfigImportError'  => __( 'Paste valid RSM block JSON export data for this block type.', 'rsm-content-widgets' ),
				'blockConfigExportReady'  => __( 'Export generated below. Copy the JSON or save it as a file.', 'rsm-content-widgets' ),
				'blockConfigExportHelp'   => __( 'Click Export JSON to generate the current block settings.', 'rsm-content-widgets' ),
				'blockConfigImportHelp'   => __( 'Paste exported JSON from another block of the same type.', 'rsm-content-widgets' ),
				'blockConfigImportApplied' => __( 'Import applied to this block.', 'rsm-content-widgets' ),
				'blockLayoutBand'         => __( 'Layout band', 'rsm-content-widgets' ),
				'bandWidth'               => __( 'Background width', 'rsm-content-widgets' ),
				'bandWidthDefault'        => __( 'Content width (default)', 'rsm-content-widgets' ),
				'bandWidthFull'           => __( 'Full screen band', 'rsm-content-widgets' ),
				'bandWidthHelp'           => __( 'Full screen stretches the background edge-to-edge. Content stays inside the theme content width.', 'rsm-content-widgets' ),
				'bandBackground'          => __( 'Band background', 'rsm-content-widgets' ),
				'bandPadTop'              => __( 'Band padding top (px)', 'rsm-content-widgets' ),
				'bandPadBottom'           => __( 'Band padding bottom (px)', 'rsm-content-widgets' ),
				'spacingTop'              => __( 'Space above block (px)', 'rsm-content-widgets' ),
				'spacingBottom'           => __( 'Space below block (px)', 'rsm-content-widgets' ),
				'spacingHelp'             => __( 'Adds margin above or below this block. Use 0 to butt blocks together.', 'rsm-content-widgets' ),
				'previewBlock'            => __( 'Preview', 'rsm-content-widgets' ),
				'clickBlockToEdit'        => __( 'Click the block to edit settings.', 'rsm-content-widgets' ),
				'returnToBlockSettings'   => __( 'Return to block settings', 'rsm-content-widgets' ),
				'previewUnavailableUntilConfigured' => __( 'Configure the block above to see a live preview.', 'rsm-content-widgets' ),
				'layout'       => __( 'Layout', 'rsm-content-widgets' ),
				'chipLayout'   => __( 'Chip layout', 'rsm-content-widgets' ),
				'chipLayoutStacked' => __( 'Stacked (icon above text)', 'rsm-content-widgets' ),
				'chipLayoutMediaLeft' => __( 'Icon left, text top-aligned', 'rsm-content-widgets' ),
				'iconAlign'    => __( 'Icon alignment', 'rsm-content-widgets' ),
				'alignLeft'    => __( 'Left', 'rsm-content-widgets' ),
				'alignCenter'  => __( 'Center', 'rsm-content-widgets' ),
				'alignRight'   => __( 'Right', 'rsm-content-widgets' ),
				'selectImage'  => __( 'Select image', 'rsm-content-widgets' ),
				'replaceImage' => __( 'Replace image', 'rsm-content-widgets' ),
				'removeImage'  => __( 'Remove image', 'rsm-content-widgets' ),
				'addItem'      => __( 'Add item', 'rsm-content-widgets' ),
				'removeItem'   => __( 'Remove', 'rsm-content-widgets' ),
				'iconHelp'     => __( 'CSS classes, e.g. dashicons dashicons-yes or fa-solid fa-check', 'rsm-content-widgets' ),
				'preview'              => __( 'Preview', 'rsm-content-widgets' ),
				'previewLoading'       => __( 'Loading preview…', 'rsm-content-widgets' ),
				'previewEmpty'         => __( 'No preview output. Check widget settings or enable the widget under Content Widgets → Widgets.', 'rsm-content-widgets' ),
				'previewError'         => __( 'Preview could not be loaded.', 'rsm-content-widgets' ),
				'previewUnavailable'   => __( 'Live preview is unavailable in this editor.', 'rsm-content-widgets' ),
				'previewFrontEndHint'  => __( 'Save and use View / Preview on the front end if this looks wrong.', 'rsm-content-widgets' ),
				'feature'      => __( 'Feature', 'rsm-content-widgets' ),
				'tab'          => __( 'Tab', 'rsm-content-widgets' ),
				'section'      => __( 'Section', 'rsm-content-widgets' ),
				'accordionCategory' => __( 'Category', 'rsm-content-widgets' ),
				'accordionRowType' => __( 'Row type', 'rsm-content-widgets' ),
				'columns'      => __( 'Columns', 'rsm-content-widgets' ),
				'sectionTitle' => __( 'Section title', 'rsm-content-widgets' ),
				'accentColor'  => __( 'Accent color', 'rsm-content-widgets' ),
				'iconClasses'  => __( 'Icon classes', 'rsm-content-widgets' ),
				'selectIcon'   => __( 'Icon', 'rsm-content-widgets' ),
				'iconStyle'    => __( 'Icon style', 'rsm-content-widgets' ),
				'iconStyleGlyph' => __( 'Icon glyph', 'rsm-content-widgets' ),
				'iconStyleBadge' => __( 'Number in circle', 'rsm-content-widgets' ),
				'iconColor'    => __( 'Icon color', 'rsm-content-widgets' ),
				'badgeCircleColor' => __( 'Badge circle color', 'rsm-content-widgets' ),
				'badgeTextColor' => __( 'Badge number color', 'rsm-content-widgets' ),
				'badgeGlyph'   => __( 'Badge character', 'rsm-content-widgets' ),
				'badgeGlyphHelp' => __( 'For Number in circle: enter 0-9 when the icon picker does not provide fa-4.', 'rsm-content-widgets' ),
				'customIcon'   => __( 'Custom icon classes', 'rsm-content-widgets' ),
				'iconImage'    => __( 'Icon image', 'rsm-content-widgets' ),
				'box'          => __( 'Box', 'rsm-content-widgets' ),
				'description'  => __( 'Description', 'rsm-content-widgets' ),
				'title'        => __( 'Title', 'rsm-content-widgets' ),
				'popouts'      => __( 'Chip popouts', 'rsm-content-widgets' ),
				'enablePopouts' => __( 'Enable chip popouts', 'rsm-content-widgets' ),
				'enablePopoutsHelp' => __( 'Hot chips open a detail modal. Mark chips as Hot, then add matching popout rows below.', 'rsm-content-widgets' ),
				'popoutHot'    => __( 'Hot (opens popout)', 'rsm-content-widgets' ),
				'popout'       => __( 'Popout', 'rsm-content-widgets' ),
				'addPopout'    => __( 'Add popout', 'rsm-content-widgets' ),
				'chipLabel'    => __( 'Chip label', 'rsm-content-widgets' ),
				'chipLabelHelp' => __( 'Optional. Match the feature title when chip order differs.', 'rsm-content-widgets' ),
				'popoutTitle'  => __( 'Popout title', 'rsm-content-widgets' ),
				'popoutSubtitle' => __( 'Popout sub-headline', 'rsm-content-widgets' ),
				'popoutContent' => __( 'Popout content', 'rsm-content-widgets' ),
				'imageLightbox' => __( 'Open in lightbox', 'rsm-content-widgets' ),
				'imageLightboxHelp' => __( 'Click to view the full-size image in an overlay. Disabled when a link URL is set.', 'rsm-content-widgets' ),
				'imageLightboxCaption' => __( 'Lightbox caption', 'rsm-content-widgets' ),
				'heroLayers'       => __( 'Background layers', 'rsm-content-widgets' ),
				'heroLayersHelp'   => __( 'Stack images and video (bottom row first). Leave empty to use the single background image/video fields.', 'rsm-content-widgets' ),
				'heroLayer'        => __( 'Layer', 'rsm-content-widgets' ),
				'heroLayerType'    => __( 'Layer type', 'rsm-content-widgets' ),
				'heroLayerImage'   => __( 'Image', 'rsm-content-widgets' ),
				'heroLayerVideo'   => __( 'Video (MP4)', 'rsm-content-widgets' ),
				'heroLayerVideoUrl' => __( 'Video URL', 'rsm-content-widgets' ),
				'heroLayerImageField' => __( 'Layer image', 'rsm-content-widgets' ),
				'heroLayerOpacity' => __( 'Opacity', 'rsm-content-widgets' ),
				'heroLayerBlend'   => __( 'Blend mode', 'rsm-content-widgets' ),
				'heroLayerFocal'   => __( 'Focal point', 'rsm-content-widgets' ),
				'heroLayerParallax' => __( 'Parallax this layer', 'rsm-content-widgets' ),
				'addHeroLayer'     => __( 'Add layer', 'rsm-content-widgets' ),
				'heroPresets'      => __( 'Presets', 'rsm-content-widgets' ),
				'heroPresetSelect' => __( 'Layout preset', 'rsm-content-widgets' ),
				'heroPresetApply'  => __( 'Apply preset', 'rsm-content-widgets' ),
				'heroPresetApplyHelp' => __( 'Overwrites layout and appearance fields. Your background image is kept when the preset does not include one.', 'rsm-content-widgets' ),
				'heroPresetExport' => __( 'Export JSON', 'rsm-content-widgets' ),
				'heroPresetImport' => __( 'Import JSON', 'rsm-content-widgets' ),
				'heroPresetImportApply' => __( 'Apply import', 'rsm-content-widgets' ),
				'heroPresetCopied' => __( 'Hero JSON copied to clipboard.', 'rsm-content-widgets' ),
				'heroPresetImportError' => __( 'Paste valid RSM Hero JSON export data.', 'rsm-content-widgets' ),
				'heroPresetConfirm' => __( 'Apply this hero preset? Layout and appearance values will be overwritten.', 'rsm-content-widgets' ),
				'heroPresetApplied' => __( 'Preset applied. Add or keep your background image as needed.', 'rsm-content-widgets' ),
				'heroPresetImportApplied' => __( 'Import applied to this hero block.', 'rsm-content-widgets' ),
				'heroPresetExportReady' => __( 'Export generated below. Copy the JSON or save it as a file.', 'rsm-content-widgets' ),
				'heroPresetExportHelp' => __( 'Click Export JSON to generate the current hero settings.', 'rsm-content-widgets' ),
				'heroPresetImportHelp' => __( 'Paste an RSM Hero export, then click Apply import.', 'rsm-content-widgets' ),
				'popoutMedia'          => __( 'Popout icon / image', 'rsm-content-widgets' ),
				'popoutMediaInherit' => __( 'Inherit from feature chip', 'rsm-content-widgets' ),
				'popoutMediaCustom' => __( 'Custom icon or image', 'rsm-content-widgets' ),
			),
		);

		if ( class_exists( 'Rsmspark_Buttons', false ) ) {
			$editor_config['savedButtons'] = array();
			foreach ( Rsmspark_Buttons::get_saved_buttons_options() as $id => $label ) {
				$editor_config['savedButtons'][] = array(
					'value' => (string) $id,
					'label' => (string) $label,
				);
			}
		}

		$editor_config['woocommerceAvailable'] = false;
		$editor_config['woocommerceProducts']  = array();

		if ( class_exists( 'Rsmspark_Embed_Chrome', false ) ) {
			$embed_i18n = Rsmspark_Embed_Chrome::editor_i18n();
			if ( isset( $embed_i18n['panelEmbed'] ) ) {
				$embed_i18n['embedWidthPanel'] = $embed_i18n['panelEmbed'];
			}
			$editor_config['i18n'] = array_merge(
				is_array( $editor_config['i18n'] ?? null ) ? $editor_config['i18n'] : array(),
				$embed_i18n
			);
		}

		self::attach_editor_inline_config( $editor_config, $attribute_schemas );
	}

	/**
	 * Register shared front-end handles referenced by block style/script in the editor.
	 *
	 * @return void
	 */
	public static function register_block_asset_handles() {
		if ( ! wp_style_is( 'rsmspark-content-widgets', 'registered' ) ) {
			wp_register_style(
				'rsmspark-content-widgets',
				RSMSPARK_CONTENT_WIDGETS_URL . 'assets/css/rsm-content-widgets.css',
				array(),
				RSMSPARK_CONTENT_WIDGETS_VERSION
			);

			wp_register_script(
				'rsmspark-content-widgets',
				RSMSPARK_CONTENT_WIDGETS_URL . 'assets/js/rsm-content-widgets.js',
				array(),
				RSMSPARK_CONTENT_WIDGETS_VERSION,
				true
			);
		}

		if ( ! wp_style_is( 'rsmspark-content-widgets-features-fontawesome', 'registered' ) ) {
			wp_register_style(
				'rsmspark-content-widgets-features-fontawesome',
				RSMSPARK_CONTENT_WIDGETS_URL . 'assets/vendor/fontawesome/css/all.min.css',
				array(),
				'6.5.2'
			);
		}

		if ( ! wp_style_is( 'rsmspark-content-widgets-features', 'registered' ) ) {
			wp_register_style(
				'rsmspark-content-widgets-features',
				RSMSPARK_CONTENT_WIDGETS_URL . 'assets/css/rsm-features.css',
				array( 'rsmspark-content-widgets-features-fontawesome', 'dashicons', 'rsmspark-content-widgets' ),
				RSMSPARK_CONTENT_WIDGETS_VERSION
			);

			wp_register_script(
				'rsmspark-content-widgets-cw-modal',
				RSMSPARK_CONTENT_WIDGETS_URL . 'assets/js/rsm-cw-modal.js',
				array(),
				RSMSPARK_CONTENT_WIDGETS_VERSION,
				true
			);

			wp_register_script(
				'rsmspark-content-widgets-features',
				RSMSPARK_CONTENT_WIDGETS_URL . 'assets/js/rsm-features.js',
				array( 'jquery', 'rsmspark-content-widgets-cw-modal' ),
				RSMSPARK_CONTENT_WIDGETS_VERSION,
				true
			);
		}

		if ( ! wp_style_is( 'rsmspark-buttons', 'registered' ) ) {
			wp_register_style(
				'rsmspark-buttons',
				RSMSPARK_CONTENT_WIDGETS_URL . 'assets/css/rsm-buttons.css',
				array( 'dashicons' ),
				RSMSPARK_CONTENT_WIDGETS_VERSION
			);
		}

		if ( class_exists( 'Rsmspark_Rich_Text_Assets', false ) ) {
			Rsmspark_Rich_Text_Assets::register_front();
		}
	}

	/**
	 * Front-end asset handles for a block slug (inserter / editor preview styling).
	 *
	 * @param string $name Block slug without namespace.
	 * @return array{style: array<int, string>, script: array<int, string>}
	 */
	public static function block_asset_handles( $name ) {
		$name    = sanitize_key( (string) $name );
		$styles  = array( 'rsmspark-content-widgets' );
		$scripts = array( 'rsmspark-content-widgets' );

		if ( 'features' === $name ) {
			$styles[]  = 'dashicons';
			$styles[]  = 'rsmspark-content-widgets-features-fontawesome';
			$styles[]  = 'rsmspark-content-widgets-features';
			$scripts[] = 'rsmspark-content-widgets-cw-modal';
			$scripts[] = 'rsmspark-content-widgets-features';
		} elseif ( 'footer' === $name ) {
			$styles[] = 'dashicons';
			$styles[] = 'rsmspark-content-widgets-footer-fontawesome';
			$styles[] = 'rsmspark-content-widgets-footer';
		} elseif ( 'icon-box-grid' === $name ) {
			$styles[] = 'dashicons';
		} elseif ( 'buttons' === $name ) {
			$styles[] = 'rsmspark-buttons';
			$styles[] = 'dashicons';
		} elseif ( 'accordion' === $name || 'tabs' === $name ) {
			$styles[] = 'dashicons';
		} elseif ( 'event-countdown' === $name ) {
			$styles[] = 'dashicons';
		} elseif ( 'headline' === $name ) {
			$styles[] = 'rsmspark-rich-text';
		}

		return array(
			'style'  => $styles,
			'script' => $scripts,
		);
	}

	/**
	 * Curated attribute overrides for inserter hover previews.
	 *
	 * @param string               $slug Block slug without namespace.
	 * @param array<string, mixed> $args Block registration args.
	 * @return array<string, mixed>|null
	 */
	private static function inserter_example_attributes( $slug, array $args ) {
		$slug = sanitize_key( (string) $slug );

		switch ( $slug ) {
			case 'event-countdown':
				$future = wp_date( 'Y-m-d', strtotime( '+30 days' ), wp_timezone() );
				return array(
					'title'             => __( 'Product Launch', 'rsm-content-widgets' ),
					'end_date'          => $future,
					'end_time'          => '09:00:00',
					'expired_message'   => __( 'The event has started!', 'rsm-content-widgets' ),
					'background_color'  => '#1e293b',
					'text_color'        => '#ffffff',
					'title_color'       => '#ff6b35',
				);

			case 'hero':
				return array(
					'headline'    => __( 'Welcome to your site', 'rsm-content-widgets' ),
					'subheadline' => __( 'Add a short supporting message and a call to action.', 'rsm-content-widgets' ),
					'cta_text'    => __( 'Get started', 'rsm-content-widgets' ),
					'cta_url'     => '#',
					'layout'      => 'centered',
					'min_height'  => 280,
				);

			case 'headline':
				return array(
					'overline'    => __( 'Featured', 'rsm-content-widgets' ),
					'headline'    => __( 'Your headline here', 'rsm-content-widgets' ),
					'subheadline' => __( 'Supporting copy for the section.', 'rsm-content-widgets' ),
				);

			case 'image':
				return array(
					'image_src' => self::inserter_demo_asset_url( 'inserter-demo-before' ),
					'alt_text'  => __( 'Sample image', 'rsm-content-widgets' ),
				);

			case 'buttons':
				return array(
					'source_mode' => 'custom',
					'text_line_1' => __( 'Primary action', 'rsm-content-widgets' ),
					'link_url'    => '#',
					'bg_color'    => '#ff6b35',
				);

			case 'accordion':
				return array(
					'items' => array(
						array(
							'title'   => __( 'First section', 'rsm-content-widgets' ),
							'content' => __( 'Expanded preview content.', 'rsm-content-widgets' ),
						),
						array(
							'title'   => __( 'Second section', 'rsm-content-widgets' ),
							'content' => __( 'Collapsed in preview.', 'rsm-content-widgets' ),
						),
					),
				);

			case 'tabs':
				return array(
					'items' => array(
						array(
							'title'   => __( 'Overview', 'rsm-content-widgets' ),
							'content' => __( 'Active tab preview.', 'rsm-content-widgets' ),
						),
						array(
							'title'   => __( 'Details', 'rsm-content-widgets' ),
							'content' => __( 'Second tab content.', 'rsm-content-widgets' ),
						),
					),
				);

			case 'features':
				return array(
					'title'   => __( 'Why choose us', 'rsm-content-widgets' ),
					'columns' => 3,
					'features' => array(
						array(
							'title'       => __( 'Fast setup', 'rsm-content-widgets' ),
							'description' => __( 'Launch quickly.', 'rsm-content-widgets' ),
							'icon'        => 'dashicons dashicons-yes-alt',
						),
						array(
							'title'       => __( 'Flexible', 'rsm-content-widgets' ),
							'description' => __( 'Works everywhere.', 'rsm-content-widgets' ),
							'icon'        => 'dashicons dashicons-admin-customizer',
						),
						array(
							'title'       => __( 'Supported', 'rsm-content-widgets' ),
							'description' => __( 'Real help.', 'rsm-content-widgets' ),
							'icon'        => 'dashicons dashicons-sos',
						),
					),
				);

			case 'icon-box-grid':
				return array(
					'columns' => 3,
					'items'   => array(
						array(
							'title'       => __( 'Design', 'rsm-content-widgets' ),
							'description' => __( 'Polished layouts.', 'rsm-content-widgets' ),
							'icon'        => 'dashicons dashicons-art',
						),
						array(
							'title'       => __( 'Build', 'rsm-content-widgets' ),
							'description' => __( 'Ship faster.', 'rsm-content-widgets' ),
							'icon'        => 'dashicons dashicons-hammer',
						),
					),
				);

			case 'stats-counter':
				return array(
					'items' => array(
						array(
							'value' => '120',
							'label' => __( 'Projects', 'rsm-content-widgets' ),
						),
						array(
							'value' => '98',
							'label' => __( 'Clients', 'rsm-content-widgets' ),
						),
					),
				);

			case 'svg-icon':
				return array(
					'size'  => 48,
					'color' => '#ff6b35',
					'align' => 'center',
				);

			case 'logo-grid':
				$demo = self::inserter_demo_asset_url( 'inserter-demo-before' );
				return array(
					'title'   => __( 'Trusted by', 'rsm-content-widgets' ),
					'columns' => '4',
					'items'   => array(
						array(
							'image'     => 0,
							'image_src' => $demo,
							'name'      => __( 'Client One', 'rsm-content-widgets' ),
						),
						array(
							'image'     => 0,
							'image_src' => $demo,
							'name'      => __( 'Client Two', 'rsm-content-widgets' ),
						),
						array(
							'image'     => 0,
							'image_src' => $demo,
							'name'      => __( 'Client Three', 'rsm-content-widgets' ),
						),
						array(
							'image'     => 0,
							'image_src' => $demo,
							'name'      => __( 'Client Four', 'rsm-content-widgets' ),
						),
					),
				);

			case 'testimonials-team':
				return array(
					'title'    => __( 'What our clients say', 'rsm-content-widgets' ),
					'layout'   => 'grid',
					'profiles' => array(
						array(
							'photo' => 0,
							'name'  => __( 'Alex Morgan', 'rsm-content-widgets' ),
							'role'  => __( 'Product Lead', 'rsm-content-widgets' ),
							'quote' => __( 'RSM widgets made our launch pages effortless.', 'rsm-content-widgets' ),
						),
						array(
							'photo' => 0,
							'name'  => __( 'Jordan Lee', 'rsm-content-widgets' ),
							'role'  => __( 'Designer', 'rsm-content-widgets' ),
							'quote' => __( 'Clean defaults and flexible layouts out of the box.', 'rsm-content-widgets' ),
						),
					),
				);

		}

		return null;
	}

	/**
	 * Build inserter preview example data from attribute schema defaults.
	 *
	 * @param string               $slug Block slug without namespace.
	 * @param array<string, mixed> $args Block registration args.
	 * @return array<string, mixed>
	 */
	public static function build_block_example( $slug, array $args ) {
		if ( ! empty( $args['example'] ) && is_array( $args['example'] ) ) {
			return $args['example'];
		}

		$attributes    = is_array( $args['attributes'] ?? null ) ? $args['attributes'] : array();
		$example_attrs = array();

		foreach ( $attributes as $key => $schema ) {
			if ( ! is_array( $schema ) || ! array_key_exists( 'default', $schema ) ) {
				continue;
			}
			$example_attrs[ (string) $key ] = $schema['default'];
		}

		$curated = self::inserter_example_attributes( $slug, $args );
		if ( is_array( $curated ) ) {
			$example_attrs = array_merge( $example_attrs, $curated );
		}

		return array(
			'attributes'    => $example_attrs,
			'viewportWidth' => 520,
		);
	}

	/**
	 * @return array<string, mixed>
	 */
	private static function editor_debug_state() {
		$registry = WP_Block_Type_Registry::get_instance();
		$blocked_deps = array();
		$editor_deps  = self::editor_script_dependencies();
		foreach ( $editor_deps as $handle ) {
			if ( 'wp-blocks' === $handle || 0 === strpos( $handle, 'wp-' ) ) {
				continue;
			}
			if ( ! wp_script_is( $handle, 'enqueued' ) ) {
				$blocked_deps[] = $handle . ':not_enqueued';
			}
		}

		return array(
			'plugin_version'        => RSMSPARK_CONTENT_WIDGETS_VERSION,
			'enabled'               => Rsmspark_Content_Widgets_Settings::is_enabled(),
			'blocks_enabled'        => Rsmspark_Content_Widgets_Settings::blocks_enabled(),
			'block_editor_ui'       => Rsmspark_Content_Widgets_Settings::block_editor_ui_enabled(),
			'debug_mode'            => ! empty( Rsmspark_Content_Widgets_Settings::get()['debug_mode'] ),
			'registry_count'        => count( self::$registry ),
			'headline_registered'   => $registry->is_registered( self::BLOCK_NAMESPACE . '/headline' ),
			'bootstrap_registered'  => wp_script_is( self::BOOTSTRAP_SCRIPT_HANDLE, 'registered' ),
			'bootstrap_enqueued'    => wp_script_is( self::BOOTSTRAP_SCRIPT_HANDLE, 'enqueued' ),
			'script_registered'     => wp_script_is( self::EDITOR_SCRIPT_HANDLE, 'registered' ),
			'script_enqueued'       => wp_script_is( self::EDITOR_SCRIPT_HANDLE, 'enqueued' ),
			'editor_script_deps'    => $editor_deps,
			'editor_dep_warnings'   => $blocked_deps,
			'inline_config'         => self::editor_inline_config_attached(),
			'script_url'            => RSMSPARK_CONTENT_WIDGETS_URL . 'assets/js/blocks-editor.js',
			'bootstrap_url'         => RSMSPARK_CONTENT_WIDGETS_URL . 'assets/js/blocks-editor-bootstrap.js',
		);
	}

	/**
	 * @return void
	 */
	public static function maybe_log_editor_debug() {
		if ( empty( Rsmspark_Content_Widgets_Settings::get()['debug_mode'] ) ) {
			return;
		}

		self::ensure_widget_registry();

		$state = self::editor_debug_state();

		wp_register_script(
			self::DEBUG_SCRIPT_HANDLE,
			false,
			array(),
			RSMSPARK_CONTENT_WIDGETS_VERSION,
			true
		);
		wp_enqueue_script( self::DEBUG_SCRIPT_HANDLE );
		wp_add_inline_script(
			self::DEBUG_SCRIPT_HANDLE,
			'(function(){' .
			'var s=' . wp_json_encode( $state ) . ';' .
			'console.warn("RSM Content Widgets block editor debug", s);' .
			'try{' .
			'var n=document.createElement("div");' .
			'n.setAttribute("role","note");' .
			'n.style.cssText="position:fixed;bottom:12px;left:12px;z-index:999999;max-width:420px;padding:10px 12px;background:#1e293b;color:#f8fafc;font:12px/1.4 monospace;border-radius:8px;box-shadow:0 8px 24px rgba(0,0,0,.35)";' .
			'n.textContent="CW editor debug: registry="+s.registry_count+" bootstrap="+s.bootstrap_enqueued+" script="+s.script_enqueued+" headline="+s.headline_registered+" deps="+(s.editor_script_deps?s.editor_script_deps.length:0)+" printed="+!!document.querySelector(\'script[src*="content-widgets/assets/js/blocks-editor.js"]\')+" ready="+!!window.rsmCwBlocksEditorReady;' .
			'document.body.appendChild(n);' .
			'}catch(e){}' .
			'})();',
			'after'
		);
	}

	/**
	 * @return void
	 */
	public static function maybe_render_editor_debug_notice() {
		if ( empty( Rsmspark_Content_Widgets_Settings::get()['debug_mode'] ) ) {
			return;
		}

		if ( ! function_exists( 'get_current_screen' ) ) {
			return;
		}

		$screen = get_current_screen();
		if ( ! $screen || ! method_exists( $screen, 'is_block_editor' ) || ! $screen->is_block_editor() ) {
			return;
		}

		$state = self::editor_debug_state();
		echo '<div class="notice notice-warning"><p><strong>RSM Content Widgets — block editor debug</strong></p><pre style="white-space:pre-wrap;">'
			. esc_html( wp_json_encode( $state, JSON_PRETTY_PRINT ) )
			. '</pre></div>';
	}

	/**
	 * @return void
	 */
	public static function register_all() {
		if ( ! Rsmspark_Content_Widgets_Settings::is_enabled() ) {
			return;
		}

		self::ensure_widget_registry();

		if ( ! function_exists( 'register_block_type' ) || empty( self::$registry ) ) {
			return;
		}

		static $registered = false;
		if ( $registered ) {
			return;
		}
		$registered = true;

		self::register_block_asset_handles();

		$editor_enabled = Rsmspark_Content_Widgets_Settings::blocks_enabled();

		if ( $editor_enabled ) {
			self::init_block_layout_supports();
			self::register_editor_script();
		}

		foreach ( self::$registry as $name => $args ) {
			$registry_id = Rsmspark_Content_Widgets_Registry::registry_id_from_block_slug( $name );
			if ( ! Rsmspark_Content_Widgets_Settings::is_widget_enabled( $registry_id ) ) {
				continue;
			}

			$block_args = $args;

			if ( class_exists( 'Rsmspark_Embed_Chrome', false ) ) {
				$block_args['attributes'] = self::merge_embed_block_attributes(
					is_array( $block_args['attributes'] ?? null ) ? $block_args['attributes'] : array()
				);
			}

			if ( $editor_enabled ) {
				$block_args['editor_script'] = self::block_editor_script_handles( $args );
				$block_args['editor_style']  = self::block_editor_style_handles( $args );
			} else {
				unset( $block_args['editor_script'], $block_args['editor_style'] );
			}

			$asset_handles = self::block_asset_handles( $name );
			$block_args['style']  = $asset_handles['style'];
			$block_args['script'] = $asset_handles['script'];

			if ( empty( $block_args['example'] ) ) {
				$block_args['example'] = self::build_block_example( $name, $block_args );
			}

			if ( ! empty( $block_args['render_callback'] ) && is_callable( $block_args['render_callback'] ) ) {
				$block_args['render_callback'] = self::wrap_render_callback_for_editor_preview(
					$block_args['render_callback'],
					$name
				);
			}

			$block_args = self::apply_brand_block_icon( $block_args );

			register_block_type( self::BLOCK_NAMESPACE . '/' . $name, $block_args );

			// Legacy aliases must always register server-side — editor JS always registers
			// rsmspark-cw-compat/* and existing posts rely on it. experimental_blocks only toggles
			// duplicate legacy entries in the inserter (see blocks-editor.js).
			$legacy_args = $block_args;
			if ( $editor_enabled ) {
				$legacy_args['editor_script'] = self::block_editor_script_handles( $args );
				$legacy_args['editor_style']  = self::block_editor_style_handles( $args );
			}
			if ( ! Rsmspark_Content_Widgets_Settings::experimental_blocks_enabled() ) {
				if ( ! isset( $legacy_args['supports'] ) || ! is_array( $legacy_args['supports'] ) ) {
					$legacy_args['supports'] = array();
				}
				$legacy_args['supports']['inserter'] = false;
			}
			register_block_type( self::LEGACY_BLOCK_NAMESPACE . '/' . $name, $legacy_args );
		}

		do_action( 'rsmspark_content_widgets_blocks_registered', self::$registry );
	}

	/**
	 * Ensure front-end widget assets load during block editor ServerSideRender requests.
	 *
	 * @param callable             $callback  Original render callback.
	 * @param string               $block_slug Block slug without namespace.
	 * @return callable
	 */
	private static function wrap_render_callback_for_editor_preview( $callback, $block_slug ) {
		return function ( $attributes, $content, $block ) use ( $callback, $block_slug ) {
			self::$in_block_render = true;

			try {
				if ( self::is_editor_preview_request() ) {
					self::enqueue_editor_preview_assets();
					if ( class_exists( 'Rsmspark_Content_Widgets_Assets', false ) ) {
						Rsmspark_Content_Widgets_Assets::enqueue_for_widget(
							Rsmspark_Content_Widgets_Registry::registry_id_from_block_slug( $block_slug )
						);
					}

					$attributes = self::merge_raw_block_renderer_attributes(
						$attributes,
						self::BLOCK_NAMESPACE . '/' . $block_slug
					);
					$attributes = self::merge_raw_block_renderer_attributes(
						$attributes,
						self::LEGACY_BLOCK_NAMESPACE . '/' . $block_slug
					);
				}

				$html = (string) call_user_func( $callback, $attributes, $content, $block );
				// Late escape for register_block_type render_callback returns (same allowlist as shortcodes).
				return wp_kses( $html, Rsmspark_Content_Widgets_Kses::allowed_html() );
			} finally {
				self::$in_block_render = false;
			}
		};
	}

	/**
	 * Preserve nested block attributes before REST schema validation strips them.
	 *
	 * @param mixed           $result  Response to replace.
	 * @param WP_REST_Server  $server  Server instance.
	 * @param WP_REST_Request $request Request used to generate the response.
	 * @return mixed
	 */
	public static function capture_raw_block_renderer_attributes( $result, $server, $request ) {
		unset( $server );

		if ( ! $request instanceof WP_REST_Request ) {
			return $result;
		}

		$route = (string) $request->get_route();
		if ( false === strpos( $route, '/wp/v2/block-renderer/' ) ) {
			return $result;
		}

		$name = (string) $request->get_param( 'name' );
		$body = $request->get_json_params();
		$raw  = is_array( $body['attributes'] ?? null ) ? $body['attributes'] : array();

		if ( '' !== $name && ! empty( $raw ) ) {
			self::$raw_block_renderer_attributes[ $name ] = $raw;
		}

		return $result;
	}

	/**
	 * @param mixed               $attributes Validated attributes.
	 * @param string              $block_name Full block name.
	 * @return array<string, mixed>
	 */
	private static function merge_raw_block_renderer_attributes( $attributes, $block_name ) {
		$validated = is_array( $attributes ) ? $attributes : array();
		$raw       = self::$raw_block_renderer_attributes[ $block_name ] ?? array();

		if ( empty( $raw ) || ! is_array( $raw ) ) {
			return $validated;
		}

		unset( self::$raw_block_renderer_attributes[ $block_name ] );

		$merged = array_merge( $validated, $raw );

		foreach ( array( 'features', 'popout_items', 'items', 'ids' ) as $list_key ) {
			if ( isset( $raw[ $list_key ] ) && is_array( $raw[ $list_key ] ) ) {
				$merged[ $list_key ] = $raw[ $list_key ];
			}
		}

		return $merged;
	}

	/**
	 * RSMSpark orange dashicon in the block inserter (matches SiteOrigin picker).
	 *
	 * @param array<string, mixed> $block_args Block registration args.
	 * @return array<string, mixed>
	 */
	private static function apply_brand_block_icon( array $block_args ) {
		// Inserter icon colors are applied client-side (block-editor-branding.js).
		return $block_args;
	}

	/**
	 * CW bundle plus any block-specific editor script (e.g. RSF SVG Icon).
	 *
	 * @param array<string, mixed> $args Registry args.
	 * @return string|string[]
	 */
	private static function block_editor_script_handles( array $args ) {
		$custom = isset( $args['editor_script'] ) ? (string) $args['editor_script'] : '';
		if ( '' !== $custom && self::EDITOR_SCRIPT_HANDLE !== $custom ) {
			return array( self::EDITOR_SCRIPT_HANDLE, $custom );
		}

		return self::EDITOR_SCRIPT_HANDLE;
	}

	/**
	 * Shared editor styles plus any block-specific editor style.
	 *
	 * @param array<string, mixed> $args Registry args.
	 * @return string|string[]
	 */
	private static function block_editor_style_handles( array $args ) {
		$styles = array( 'rsmspark-content-widgets-blocks-editor' );
		$custom = isset( $args['editor_style'] ) ? (string) $args['editor_style'] : '';
		if (
			'' !== $custom
			&& 'rsmspark-content-widgets-blocks-editor' !== $custom
			&& ! in_array( $custom, $styles, true )
		) {
			$styles[] = $custom;
		}

		return $styles;
	}

	/**
	 * @return array<string, array<string, mixed>>
	 */
	public static function definitions() {
		return self::$registry;
	}
}
