<?php

/**
 * AnchorKit Gutenberg Blocks
 *
 * Registers and handles Gutenberg block functionality
 */

// Prevent direct access
if (!defined('ABSPATH')) {
	exit;
}

/**
 * Recursively sanitize block attribute values.
 *
 * @param mixed $value
 * @return mixed
 */
function anchorkit_sanitize_block_attribute_value($value)
{
	if (is_bool($value) || is_int($value) || is_float($value)) {
		return $value;
	}

	if (is_string($value)) {
		return sanitize_text_field($value);
	}

	if (is_array($value)) {
		foreach ($value as $key => $child_value) {
			$value[$key] = anchorkit_sanitize_block_attribute_value($child_value);
		}

		return $value;
	}

	return $value;
}

/**
 * Sanitize all attributes passed to the Gutenberg block renderer/preview.
 *
 * @param array $attributes
 * @return array
 */
function anchorkit_sanitize_block_attributes($attributes)
{
	if (!is_array($attributes)) {
		return array();
	}

	$sanitized = array();
	foreach ($attributes as $key => $value) {
		if (in_array($key, array('className', 'customCssClass'), true) && is_string($value)) {
			$classes = preg_split('/\s+/', $value);
			$classes = array_filter(array_map('sanitize_html_class', $classes));
			$sanitized[$key] = implode(' ', $classes);
			continue;
		}

		$sanitized[$key] = anchorkit_sanitize_block_attribute_value($value);
	}

	return $sanitized;
}

/**
 * Register AnchorKit Gutenberg blocks
 */
function anchorkit_register_blocks()
{
	// Check if Gutenberg is available
	if (!function_exists('register_block_type')) {
		return;
	}

	// Register the Table of Contents block
	register_block_type(
		'anchorkit/table-of-contents',
		array(
			'attributes' => array(
				'preview' => array(
					'type' => 'boolean',
					'default' => false,
				),
				'align' => array(
					'type' => 'string',
				),
				// Content Settings
				'title' => array(
					'type' => 'string',
					'default' => 'Table of Contents',
				),
				'showTitle' => array(
					'type' => 'boolean',
					'default' => true,
				),
				'headingLevels' => array(
					'type' => 'array',
					'default' => array('h2', 'h3', 'h4'),
				),
				'minHeadingsToShow' => array(
					'type' => 'number',
					'default' => 2,
				),
				'excludeSelectors' => array(
					'type' => 'string',
					'default' => '',
				),
				'excludeRegex' => array(
					'type' => 'string',
					'default' => '',
				),
				// Behavior Settings
				'collapsible' => array(
					'type' => 'boolean',
					'default' => true,
				),
				'initialState' => array(
					'type' => 'string',
					'default' => 'expanded',
				),
				'smoothScroll' => array(
					'type' => 'boolean',
					'default' => true,
				),
				'scrollOffset' => array(
					'type' => 'number',
					'default' => 0,
				),
				'scrollEasing' => array(
					'type' => 'string',
					'default' => 'ease-in-out',
				),
				'scrollDuration' => array(
					'type' => 'number',
					'default' => 500,
				),
				'hierarchicalView' => array(
					'type' => 'boolean',
					'default' => true,
				),
				// Style & Appearance
				'stylePreset' => array(
					'type' => 'string',
					'default' => 'inherit',
				),
				'bulletStyle' => array(
					'type' => 'string',
					'default' => 'disc',
				),
				'bulletCharacter' => array(
					'type' => 'string',
					'default' => '•',
				),
				'bulletColor' => array(
					'type' => 'string',
					'default' => '#0073AA',
				),
				'showNumerals' => array(
					'type' => 'boolean',
					'default' => false,
				),
				'numberingStyle' => array(
					'type' => 'string',
					'default' => 'hierarchical',
				),
				'numberingFormat' => array(
					'type' => 'string',
					'default' => 'decimal',
				),
				'numberingSeparator' => array(
					'type' => 'string',
					'default' => '.',
				),
				// Design override scaffold
				'designOverride' => array(
					'type' => 'boolean',
					'default' => false,
				),
				'theme' => array(
					'type' => 'string',
					'default' => 'inherit',
				),
				'boxShadowEnabled' => array(
					'type' => 'boolean',
					'default' => false,
				),
				'containerPadding' => array(
					'type' => 'number',
					'default' => null,
				),
				'containerBorderWidth' => array(
					'type' => 'number',
					'default' => null,
				),
				'containerBorderRadius' => array(
					'type' => 'number',
					'default' => null,
				),
				// Per-block Colors (Light) - defaults match auto-inserted TOC custom styling
				'bgColorLight' => array(
					'type' => 'string',
					'default' => '#FFFFFF',
				),
				'textColorLight' => array(
					'type' => 'string',
					'default' => '#333333',
				),
				'linkColorLight' => array(
					'type' => 'string',
					'default' => '#0073AA',
				),
				'linkHoverColorLight' => array(
					'type' => 'string',
					'default' => '#005177',
				),
				'borderColorLight' => array(
					'type' => 'string',
					'default' => '#DDDDDD',
				),
				'bulletColorLight' => array(
					'type' => 'string',
					'default' => '#0073AA',
				),
				// Per-block Colors (Dark) - defaults match auto-inserted TOC custom styling
				'bgColorDark' => array(
					'type' => 'string',
					'default' => '#1e1e1e',
				),
				'textColorDark' => array(
					'type' => 'string',
					'default' => '#e0e0e0',
				),
				'linkColorDark' => array(
					'type' => 'string',
					'default' => '#7ec4ee',
				),
				'linkHoverColorDark' => array(
					'type' => 'string',
					'default' => '#a8d8f0',
				),
				'borderColorDark' => array(
					'type' => 'string',
					'default' => '#404040',
				),
				'bulletColorDark' => array(
					'type' => 'string',
					'default' => '#7ec4ee',
				),
				// Box shadow components
				'shadowHOffsetLight' => array(
					'type' => 'number',
					'default' => 0,
				),
				'shadowVOffsetLight' => array(
					'type' => 'number',
					'default' => 4,
				),
				'shadowBlurLight' => array(
					'type' => 'number',
					'default' => 6,
				),
				'shadowSpreadLight' => array(
					'type' => 'number',
					'default' => 0,
				),
				'shadowColorLight' => array(
					'type' => 'string',
					'default' => '#000000',
				),
				'shadowOpacityLight' => array(
					'type' => 'number',
					'default' => 0.1,
				),
				'shadowHOffsetDark' => array(
					'type' => 'number',
					'default' => 0,
				),
				'shadowVOffsetDark' => array(
					'type' => 'number',
					'default' => 2,
				),
				'shadowBlurDark' => array(
					'type' => 'number',
					'default' => 8,
				),
				'shadowSpreadDark' => array(
					'type' => 'number',
					'default' => 0,
				),
				'shadowColorDark' => array(
					'type' => 'string',
					'default' => '#000000',
				),
				'shadowOpacityDark' => array(
					'type' => 'number',
					'default' => 0.3,
				),
				'entranceAnimation' => array(
					'type' => 'boolean',
					'default' => false,
				),
				'animationType' => array(
					'type' => 'string',
					'default' => 'fade',
				),
				// Advanced Settings
				'hideOnMobile' => array(
					'type' => 'boolean',
					'default' => false,
				),
				'mobileBreakpoint' => array(
					'type' => 'number',
					'default' => 782,
				),
				'tocWidth' => array(
					'type' => 'number',
					'default' => 100,
				),
				'tocMaxWidth' => array(
					'type' => 'number',
					'default' => 600,
				),
				'tocMinWidth' => array(
					'type' => 'number',
					'default' => 280,
				),
				'tocAlignment' => array(
					'type' => 'string',
					'default' => 'center',
				),
				'tocFloat' => array(
					'type' => 'string',
					'default' => 'none',
				),
				'ariaLabel' => array(
					'type' => 'string',
				),
				'customCssClass' => array(
					'type' => 'string',
					'default' => '',
				),
				'customId' => array(
					'type' => 'string',
					'default' => '',
				),
				'customLabels' => array(
					'type' => 'string',
				),
				// PRO Settings - Sticky TOC
				'sticky' => array(
					'type' => 'boolean',
				),
				'stickyPosition' => array(
					'type' => 'string',
				),
				'stickyOffset' => array(
					'type' => 'number',
				),
				'scrollSpy' => array(
					'type' => 'boolean',
					'default' => true,
				),
				// PRO Settings - View More
				'viewMoreEnabled' => array(
					'type' => 'boolean',
					'default' => false,
				),
				'viewMoreInitialCount' => array(
					'type' => 'number',
					'default' => 5,
				),
				'viewMoreText' => array(
					'type' => 'string',
					'default' => 'View More',
				),
				'viewLessText' => array(
					'type' => 'string',
					'default' => 'View Less',
				),
				// PRO Settings - Reading Time
				'showReadingTime' => array(
					'type' => 'boolean',
				),
				'readingSpeed' => array(
					'type' => 'number',
				),
				'showWordCount' => array(
					'type' => 'boolean',
				),
				'timeFormat' => array(
					'type' => 'string',
				),
				// PRO Settings - Advanced Typography
				'advancedTypographyOverride' => array(
					'type' => 'boolean',
					'default' => false,
				),
				'titleFontSize' => array(
					'type' => 'number',
					'default' => 20,
				),
				'h2FontSize' => array(
					'type' => 'number',
					'default' => 18,
				),
				'h3FontSize' => array(
					'type' => 'number',
					'default' => 16,
				),
				'h4FontSize' => array(
					'type' => 'number',
					'default' => 14,
				),
				'h5FontSize' => array(
					'type' => 'number',
					'default' => 13,
				),
				'h6FontSize' => array(
					'type' => 'number',
					'default' => 12,
				),
				'lineHeight' => array(
					'type' => 'number',
					'default' => 1.6,
				),
				'letterSpacing' => array(
					'type' => 'number',
					'default' => 0,
				),
				'textTransform' => array(
					'type' => 'string',
					'default' => 'none',
				),
				'linkUnderline' => array(
					'type' => 'string',
					'default' => 'none',
				),
				// PRO Settings - Back to Top
				'backToTopLink' => array(
					'type' => 'boolean',
				),
				'backToTopText' => array(
					'type' => 'string',
				),
				'backToTopFontSize' => array(
					'type' => 'number',
				),

				'excludeKeywords' => array(
					'type' => 'string',
					'default' => '',
				),
				'schemaEnabled' => array(
					'type' => 'boolean',
				),
				'schemaType' => array(
					'type' => 'string',
				),
				// PRO Settings - ACF Integration
				'acfEnabled' => array(
					'type' => 'boolean',
				),
				'acfFieldNames' => array(
					'type' => 'string',
				),
				'acfMergeMode' => array(
					'type' => 'string',
				),
				// PRO Settings - AMP
				'ampEnabled' => array(
					'type' => 'boolean',
				),
				// Anchor Settings
				'anchorFormat' => array(
					'type' => 'string',
				),
				'anchorPrefix' => array(
					'type' => 'string',
				),
			),
			'render_callback' => 'anchorkit_render_toc_block',
			'editor_script' => 'anchorkit-toc-block',
			'editor_style' => 'anchorkit-block-editor',
		)
	);
}
add_action('init', 'anchorkit_register_blocks');

/**
 * Check if post has TOC block and add ID injection filter early
 */
function anchorkit_maybe_inject_heading_ids()
{
	// Only run on frontend singular pages
	if (is_admin() || !is_singular()) {
		return;
	}

	global $post;
	if (!$post) {
		return;
	}

	$has_gutenberg_block = has_block('anchorkit/table-of-contents', $post);

	// When a Gutenberg block is present, capture its anchor settings early so heading IDs honor them.
	if ($has_gutenberg_block && function_exists('anchorkit_extract_block_anchor_settings_from_content')) {
		$block_anchor_settings = anchorkit_extract_block_anchor_settings_from_content($post->post_content);

		if (!empty($block_anchor_settings)) {
			anchorkit_set_runtime_anchor_settings(
				isset($block_anchor_settings['anchor_format']) ? $block_anchor_settings['anchor_format'] : null,
				isset($block_anchor_settings['anchor_prefix']) ? $block_anchor_settings['anchor_prefix'] : null,
				isset($block_anchor_settings['heading_levels']) ? $block_anchor_settings['heading_levels'] : null,
				true
			);

			// CRITICAL: Store block heading levels so anchorkit_inject_heading_ids uses the same levels as the block
			// This ensures sequential numbering matches across block and content
			if (!empty($block_anchor_settings['heading_levels'])) {
				add_filter(
					'anchorkit_inject_heading_levels',
					function () use ($block_anchor_settings) {
						return $block_anchor_settings['heading_levels'];
					}
				);
			}
		}
	}

	// Check if post has the TOC block or shortcode
	if ($has_gutenberg_block || has_shortcode($post->post_content, 'anchorkit_toc')) {
		// Add the filter to inject IDs into headings
		if (!has_filter('the_content', 'anchorkit_inject_heading_ids')) {
			add_filter('the_content', 'anchorkit_inject_heading_ids', 5);
		}
	}
}
add_action('wp', 'anchorkit_maybe_inject_heading_ids');

/**
 * Render callback for the Table of Contents block
 *
 * @param array $attributes Block attributes
 * @return string Block HTML output
 */
function anchorkit_render_toc_block($attributes)
{
	// Recursion guard - prevent infinite loops during block rendering
	static $is_rendering = false;
	if ($is_rendering) {
		return '';
	}
	$is_rendering = true;

	// Per-post guard - prevent duplicate TOC rendering on the same post (frontend only)
	static $rendered_posts = array();
	$is_editor_request = is_admin() || (defined('REST_REQUEST') && REST_REQUEST);

	if (!$is_editor_request) {
		global $post;
		$current_post_id = $post ? $post->ID : 0;
		if ($current_post_id > 0 && isset($rendered_posts[$current_post_id])) {
			$is_rendering = false;
			return ''; // Already rendered TOC for this post
		}
		if ($current_post_id > 0) {
			$rendered_posts[$current_post_id] = true;
		}
	}

	static $block_instance_counter = 0;
	++$block_instance_counter;
	$attributes = anchorkit_sanitize_block_attributes($attributes);
	$instance_id = 'anchorkit-toc-block-' . $block_instance_counter;

	// Avoid rendering on archives (e.g., /blog) so the TOC only appears on single entries.
	if (!$is_editor_request && !is_singular()) {
		$is_rendering = false;
		return '';
	}
	// Debug: Log attributes to understand what's being passed

	// Check if TOC is enabled
	if (!anchorkit_get_option('anchorkit_toc_enabled', false)) {
		// Show a message in the editor
		$is_rendering = false;
		if (is_admin() || (defined('REST_REQUEST') && REST_REQUEST)) {
			return '<div class="anchorkit-block-notice">
                <p><strong>AnchorKit:</strong> Table of Contents is currently disabled. Enable it in the plugin settings to display it on your site.</p>
            </div>';
		}
		return '';
	}

	// For block editor preview, show a placeholder
	if (isset($attributes['preview']) && $attributes['preview']) {
		$is_rendering = false;
		return anchorkit_toc_block_preview($attributes);
	}

	// Use the same generation function as Elementor widget
	// Map block attributes to the expected settings format
	// Combine native Gutenberg Advanced class (className) with our customCssClass
	$native_class = isset($attributes['className']) ? trim($attributes['className']) : '';
	$custom_class_from_attr = isset($attributes['customCssClass']) ? trim($attributes['customCssClass']) : '';
	$combined_custom_class = trim($custom_class_from_attr . ' ' . $native_class);

	// Determine heading levels to extract based on checkbox selection
	$heading_levels_option = isset($attributes['headingLevels']) && is_array($attributes['headingLevels'])
		? $attributes['headingLevels']
		: array('h2', 'h3', 'h4');

	$valid_levels = array('h1', 'h2', 'h3', 'h4', 'h5', 'h6');
	$heading_levels_to_extract = array_values(array_intersect($valid_levels, $heading_levels_option));
	if (empty($heading_levels_to_extract)) {
		$heading_levels_to_extract = array('h2', 'h3', 'h4');
	}

	$global_style_preset = anchorkit_get_option('anchorkit_toc_style_preset', 'minimal');
	// Free version defaults for pro features
	$global_sticky_enabled = false;
	$global_sticky_position = 'content';
	$global_sticky_offset = 20;
	$global_back_to_top_link = false;
	$global_back_to_top_text = anchorkit_get_option('anchorkit_toc_back_to_top_text', 'Back to Top');
	$global_back_to_top_font_size = (int) anchorkit_get_option('anchorkit_toc_back_to_top_font_size', 14);
	$global_show_reading_time = false;
	$global_show_word_count = false;
	$global_reading_speed = (int) anchorkit_get_option('anchorkit_toc_reading_speed', 200);
	$global_time_format = anchorkit_get_option('anchorkit_toc_time_format', 'min_read');
	$global_anchor_format = sanitize_key(anchorkit_get_option('anchorkit_toc_anchor_format', 'auto'));

	// Premium feature overrides - this entire block is stripped from free version
	if (anchorkit_fs() && anchorkit_fs()->is__premium_only()) {
		if (anchorkit_fs()->can_use_premium_code()) {
			$global_sticky_enabled = (bool) anchorkit_get_option('anchorkit_toc_sticky_enabled', false);
			$global_sticky_position = anchorkit_get_option('anchorkit_toc_sticky_position', 'content');
			$global_sticky_offset = (int) anchorkit_get_option('anchorkit_toc_sticky_offset', 20);
			$global_back_to_top_link = (bool) anchorkit_get_option('anchorkit_toc_back_to_top_link', false);
			$global_show_reading_time = (bool) anchorkit_get_option('anchorkit_toc_show_reading_time', false);
			$global_show_word_count = (bool) anchorkit_get_option('anchorkit_toc_show_word_count', false);
		}
	}
	if (!in_array($global_anchor_format, array('auto', 'sequential', 'prefixed'), true)) {
		$global_anchor_format = 'auto';
	}
	$global_anchor_prefix_raw = anchorkit_get_option('anchorkit_toc_anchor_prefix', 'section');
	$global_anchor_prefix = sanitize_title_with_dashes($global_anchor_prefix_raw);
	if ($global_anchor_prefix === '') {
		$global_anchor_prefix = 'section';
	}

	$min_headings_required = isset($attributes['minHeadingsToShow'])
		? max(1, (int) $attributes['minHeadingsToShow'])
		: (int) anchorkit_get_option('anchorkit_toc_min_headings', 2);

	$design_override = !empty($attributes['designOverride']);

	$bullet_color = ($design_override)
		? ''
		: (isset($attributes['bulletColor']) && !empty($attributes['bulletColor']) ? $attributes['bulletColor'] : '');

	// Get theme preference: per-block override falls back to global
	$global_theme_preference = anchorkit_get_option('anchorkit_toc_theme', 'system');
	$attr_theme = isset($attributes['theme']) ? trim($attributes['theme']) : 'inherit';
	$effective_theme = in_array($attr_theme, array('system', 'light', 'dark'), true) ? $attr_theme : $global_theme_preference;

	$has_anchor_override = array_key_exists('anchorFormat', $attributes) && $attributes['anchorFormat'] !== '' && $attributes['anchorFormat'] !== null;
	$anchor_format = $has_anchor_override ? sanitize_key($attributes['anchorFormat']) : $global_anchor_format;
	if (!in_array($anchor_format, array('auto', 'sequential', 'prefixed'), true)) {
		$anchor_format = $global_anchor_format;
	}

	$block_anchor_prefix = array_key_exists('anchorPrefix', $attributes) ? $attributes['anchorPrefix'] : '';
	$block_anchor_prefix = is_string($block_anchor_prefix) ? trim($block_anchor_prefix) : '';
	$normalized_block_prefix = $block_anchor_prefix !== '' ? sanitize_title_with_dashes($block_anchor_prefix) : '';
	$effective_anchor_prefix = $normalized_block_prefix !== '' ? $normalized_block_prefix : $global_anchor_prefix;

	$aria_option = anchorkit_get_option('anchorkit_toc_aria_label', '');
	$global_aria_label = is_string($aria_option) ? trim($aria_option) : '';
	$valid_schema_types = array('Article', 'BlogPosting', 'WebPage', 'HowTo', 'FAQPage', 'NewsArticle', 'TechArticle', 'Course');
	$schema_type_option = anchorkit_get_option('anchorkit_toc_schema_type', 'Article');
	$per_post_type_schema = anchorkit_get_option('anchorkit_toc_schema_type_per_post_type', array());
	global $post;
	if ($post && isset($post->post_type) && isset($per_post_type_schema[$post->post_type]) && !empty($per_post_type_schema[$post->post_type])) {
		$schema_type_option = $per_post_type_schema[$post->post_type];
	}
	if (!in_array($schema_type_option, $valid_schema_types, true)) {
		$schema_type_option = 'Article';
	}

	$default_title = isset($attributes['title']) ? $attributes['title'] : 'Table of Contents';
	$aria_label_from_attr = array_key_exists('ariaLabel', $attributes) ? trim((string) $attributes['ariaLabel']) : '';
	$effective_aria_label = $aria_label_from_attr !== ''
		? $aria_label_from_attr
		: ($global_aria_label !== '' ? $global_aria_label : $default_title);

	// Helper to determine if an attribute was explicitly set by the user
	// vs using the default value from block registration
	$is_explicitly_set = function ($attr_name, $block_default) use ($attributes) {
		// If attribute doesn't exist in the attributes array, it's not set
		if (!array_key_exists($attr_name, $attributes)) {
			return false;
		}

		// Get the attribute value
		$attr_value = $attributes[$attr_name];

		// For array attributes, check if they're the same as block default
		if (is_array($block_default) && is_array($attr_value)) {
			// If arrays are different, user explicitly set it
			return $attr_value !== $block_default;
		}

		// For scalar values, if it matches block default exactly, assume not explicitly set
		// This is a heuristic: if user set it to match the block default, we treat as "not set"
		// The alternative would require tracking change history, which blocks don't support
		if ($attr_value === $block_default) {
			return false;
		}

		// Otherwise, consider it explicitly set
		return true;
	};

	$settings = array(
		'context' => 'gutenberg',
		'theme' => $effective_theme, // Pass theme preference for light/dark/system mode
		'title' => $default_title,
		'show_title' => $is_explicitly_set('showTitle', true)
			? (bool) $attributes['showTitle']
			: (anchorkit_get_option('anchorkit_toc_show_label', null) !== null
				? (bool) anchorkit_get_option('anchorkit_toc_show_label', true)
				: true),
		'heading_levels' => $heading_levels_to_extract,
		'exclude_selectors' => $is_explicitly_set('excludeSelectors', '')
			? $attributes['excludeSelectors']
			: anchorkit_get_option('anchorkit_toc_exclude_selectors', ''),
		'exclude_regex' => $is_explicitly_set('excludeRegex', '')
			? $attributes['excludeRegex']
			: anchorkit_get_option('anchorkit_toc_exclude_regex', ''),
		// For collapsible, use direct check for attribute existence (like hierarchicalView)
		// This ensures user's explicit choice is respected even if it matches the default
		'collapsible' => array_key_exists('collapsible', $attributes)
			? (bool) $attributes['collapsible']
			: (bool) anchorkit_get_option('anchorkit_toc_collapsible', true),
		'initial_state' => $is_explicitly_set('initialState', 'expanded')
			? $attributes['initialState']
			: anchorkit_get_option('anchorkit_toc_initial_state', 'expanded'),
		// For smooth_scroll, use direct check for attribute existence
		'smooth_scroll' => array_key_exists('smoothScroll', $attributes)
			? (bool) $attributes['smoothScroll']
			: (bool) anchorkit_get_option('anchorkit_toc_smooth_scroll', true),
		'scroll_offset' => $is_explicitly_set('scrollOffset', 0)
			? (int) $attributes['scrollOffset']
			: (int) anchorkit_get_option('anchorkit_toc_scroll_offset', 0),
		'scroll_easing' => $is_explicitly_set('scrollEasing', 'ease-in-out')
			? $attributes['scrollEasing']
			: anchorkit_get_option('anchorkit_toc_scroll_easing', 'ease-in-out'),
		'scroll_duration' => $is_explicitly_set('scrollDuration', 500)
			? (int) $attributes['scrollDuration']
			: (int) anchorkit_get_option('anchorkit_toc_scroll_duration', 500),
		// For hierarchical view, use direct check for attribute existence
		'hierarchical' => array_key_exists('hierarchicalView', $attributes)
			? (bool) $attributes['hierarchicalView']
			: (bool) anchorkit_get_option('anchorkit_toc_hierarchical_view', true),
		'hierarchical_view' => array_key_exists('hierarchicalView', $attributes)
			? (bool) $attributes['hierarchicalView']
			: (bool) anchorkit_get_option('anchorkit_toc_hierarchical_view', true),
		'style_preset' => (isset($attributes['stylePreset']) && $attributes['stylePreset'] !== '' && $attributes['stylePreset'] !== 'inherit')
			? $attributes['stylePreset']
			: $global_style_preset,
		'bullet_style' => $is_explicitly_set('bulletStyle', 'disc')
			? $attributes['bulletStyle']
			: anchorkit_get_option('anchorkit_toc_bullet_style', 'disc'),
		'bullet_character' => $is_explicitly_set('bulletCharacter', '•')
			? $attributes['bulletCharacter']
			: anchorkit_get_option('anchorkit_toc_bullet_character', '•'),
		'bullet_color' => $bullet_color,
		// Bullet colors now use global theme-aware settings (bullet_color_light/dark)
		// For numerals settings, directly check if attribute exists (sent from JS)
		'show_numerals' => array_key_exists('showNumerals', $attributes)
			? (bool) $attributes['showNumerals']
			: (bool) anchorkit_get_option('anchorkit_toc_show_numerals', false),
		'numbering_style' => array_key_exists('numberingStyle', $attributes)
			? $attributes['numberingStyle']
			: anchorkit_get_option('anchorkit_toc_numbering_style', 'hierarchical'),
		'numbering_format' => array_key_exists('numberingFormat', $attributes)
			? $attributes['numberingFormat']
			: anchorkit_get_option('anchorkit_toc_numbering_format', 'decimal'),
		'numbering_separator' => array_key_exists('numberingSeparator', $attributes)
			? $attributes['numberingSeparator']
			: anchorkit_get_option('anchorkit_toc_numbering_separator', '.'),
		'hide_on_mobile' => $is_explicitly_set('hideOnMobile', false)
			? (bool) $attributes['hideOnMobile']
			: (bool) anchorkit_get_option('anchorkit_toc_hide_on_mobile', false),
		'mobile_breakpoint' => $is_explicitly_set('mobileBreakpoint', 782)
			? (int) $attributes['mobileBreakpoint']
			: (int) anchorkit_get_option('anchorkit_toc_mobile_breakpoint', 782),
		// Use block's tocWidth directly - don't fall back to global settings
		// This ensures width slider works at all values including 100% default
		'toc_width' => isset($attributes['tocWidth']) && is_numeric($attributes['tocWidth'])
			? (int) $attributes['tocWidth']
			: 100,
		// CRITICAL FIX: Pass tocMaxWidth to the generator so max-width style is applied
		'toc_max_width' => isset($attributes['tocMaxWidth']) && is_numeric($attributes['tocMaxWidth'])
			? (int) $attributes['tocMaxWidth']
			: '',
		'alignment' => $is_explicitly_set('tocAlignment', 'center')
			? $attributes['tocAlignment']
			: anchorkit_get_option('anchorkit_toc_alignment', 'center'),
		'float' => $is_explicitly_set('tocFloat', 'none')
			? $attributes['tocFloat']
			: anchorkit_get_option('anchorkit_toc_float', 'none'),
		'aria_label' => $effective_aria_label,
		'custom_css_class' => $combined_custom_class,
		'custom_id' => isset($attributes['customId']) && !empty($attributes['customId']) ? sanitize_html_class(trim($attributes['customId'])) : '',
		'custom_labels' => array_key_exists('customLabels', $attributes) ? $attributes['customLabels'] : null,
		'anchor_format' => $anchor_format,
		'anchor_prefix' => $effective_anchor_prefix,
		'min_heading_depth' => 1,
		'max_heading_depth' => 6,
		'exclude_keywords' => $is_explicitly_set('excludeKeywords', '')
			? $attributes['excludeKeywords']
			: anchorkit_get_option('anchorkit_toc_exclude_keywords', ''),
		'min_headings' => $min_headings_required,
		'instance_id' => $instance_id,
	);

	// PRO Settings - Initialized to defaults for free version
	$pro_settings = array(
		'acf_enabled' => false,
		'acf_field_names' => null,
		'acf_merge_mode' => null,
		'amp_enabled' => false,
		'sticky' => false,
		'sticky_position' => 'content',
		'sticky_offset' => 20,
		'scroll_spy' => false,
		'view_more_enabled' => false,
		'initial_count' => 5,
		'view_more_text' => 'View More',
		'view_less_text' => 'View Less',
		'entrance_animation' => false,
		'animation_type' => 'fade',
		'show_reading_time' => false,
		'reading_speed' => 200,
		'show_word_count' => false,
		'time_format' => 'min_read',
		'advanced_typography_override' => false,
		'title_font_size' => 20,
		'h2_font_size' => 18,
		'h3_font_size' => 16,
		'h4_font_size' => 14,
		'h5_font_size' => 13,
		'h6_font_size' => 12,
		'line_height' => 1.6,
		'letter_spacing' => 0,
		'text_transform' => 'none',
		'link_underline' => 'none',
		'back_to_top_link' => false,
		'back_to_top_text' => 'Back to Top',
		'back_to_top_font_size' => 14,
		'schema_enabled' => false,
		'schema_type' => 'Article',
	);

	// Premium feature overrides
	if (anchorkit_fs() && anchorkit_fs()->is__premium_only()) {
		if (anchorkit_fs()->can_use_premium_code()) {
			$pro_settings['acf_enabled'] = array_key_exists('acfEnabled', $attributes) ? (bool) $attributes['acfEnabled'] : null;
			$pro_settings['acf_field_names'] = array_key_exists('acfFieldNames', $attributes) && !empty($attributes['acfFieldNames']) ? $attributes['acfFieldNames'] : null;
			$pro_settings['acf_merge_mode'] = array_key_exists('acfMergeMode', $attributes) && !empty($attributes['acfMergeMode']) ? $attributes['acfMergeMode'] : null;
			$pro_settings['amp_enabled'] = array_key_exists('ampEnabled', $attributes) ? (bool) $attributes['ampEnabled'] : null;
			$pro_settings['sticky'] = array_key_exists('sticky', $attributes) ? (bool) $attributes['sticky'] : $global_sticky_enabled;
			$pro_settings['sticky_position'] = isset($attributes['stickyPosition']) && $attributes['stickyPosition'] !== '' ? $attributes['stickyPosition'] : $global_sticky_position;
			$pro_settings['sticky_offset'] = isset($attributes['stickyOffset']) && $attributes['stickyOffset'] !== '' ? (int) $attributes['stickyOffset'] : $global_sticky_offset;
			$pro_settings['scroll_spy'] = $is_explicitly_set('scrollSpy', true) ? (bool) $attributes['scrollSpy'] : (bool) anchorkit_get_option('anchorkit_toc_scroll_spy', true);
			$pro_settings['view_more_enabled'] = array_key_exists('viewMoreEnabled', $attributes) ? (bool) $attributes['viewMoreEnabled'] : (bool) anchorkit_get_option('anchorkit_toc_view_more_enabled', false);
			$pro_settings['initial_count'] = $is_explicitly_set('viewMoreInitialCount', 5) ? (int) $attributes['viewMoreInitialCount'] : (int) anchorkit_get_option('anchorkit_toc_initial_count', 5);
			$pro_settings['view_more_text'] = $is_explicitly_set('viewMoreText', 'View More') ? $attributes['viewMoreText'] : anchorkit_get_option('anchorkit_toc_view_more_text', 'View More');
			$pro_settings['view_less_text'] = $is_explicitly_set('viewLessText', 'View Less') ? $attributes['viewLessText'] : anchorkit_get_option('anchorkit_toc_view_less_text', 'View Less');
			$pro_settings['entrance_animation'] = $is_explicitly_set('entranceAnimation', false) ? (bool) $attributes['entranceAnimation'] : (bool) anchorkit_get_option('anchorkit_toc_entrance_animation', false);
			$pro_settings['animation_type'] = $is_explicitly_set('animationType', 'fade') ? $attributes['animationType'] : anchorkit_get_option('anchorkit_toc_animation_type', 'fade');
			$pro_settings['show_reading_time'] = array_key_exists('showReadingTime', $attributes) ? (bool) $attributes['showReadingTime'] : $global_show_reading_time;
			$pro_settings['reading_speed'] = isset($attributes['readingSpeed']) && $attributes['readingSpeed'] !== '' && $attributes['readingSpeed'] !== null ? (int) $attributes['readingSpeed'] : $global_reading_speed;
			$pro_settings['show_word_count'] = array_key_exists('showWordCount', $attributes) ? (bool) $attributes['showWordCount'] : $global_show_word_count;
			$pro_settings['time_format'] = isset($attributes['timeFormat']) && $attributes['timeFormat'] !== '' ? $attributes['timeFormat'] : $global_time_format;
			$pro_settings['advanced_typography_override'] = $is_explicitly_set('advancedTypographyOverride', false) ? (bool) $attributes['advancedTypographyOverride'] : (bool) anchorkit_get_option('anchorkit_toc_advanced_typography_override', false);
			$pro_settings['title_font_size'] = $is_explicitly_set('titleFontSize', 20) ? (int) $attributes['titleFontSize'] : (int) anchorkit_get_option('anchorkit_toc_title_font_size', 20);
			$pro_settings['h2_font_size'] = $is_explicitly_set('h2FontSize', 18) ? (int) $attributes['h2FontSize'] : (int) anchorkit_get_option('anchorkit_toc_h2_font_size', 18);
			$pro_settings['h3_font_size'] = $is_explicitly_set('h3FontSize', 16) ? (int) $attributes['h3FontSize'] : (int) anchorkit_get_option('anchorkit_toc_h3_font_size', 16);
			$pro_settings['h4_font_size'] = $is_explicitly_set('h4FontSize', 14) ? (int) $attributes['h4FontSize'] : (int) anchorkit_get_option('anchorkit_toc_h4_font_size', 14);
			$pro_settings['h5_font_size'] = $is_explicitly_set('h5FontSize', 13) ? (int) $attributes['h5FontSize'] : (int) anchorkit_get_option('anchorkit_toc_h5_font_size', 13);
			$pro_settings['h6_font_size'] = $is_explicitly_set('h6FontSize', 12) ? (int) $attributes['h6FontSize'] : (int) anchorkit_get_option('anchorkit_toc_h6_font_size', 12);
			$pro_settings['line_height'] = array_key_exists('lineHeight', $attributes) ? (float) $attributes['lineHeight'] : (float) anchorkit_get_option('anchorkit_toc_line_height', 1.6);
			$pro_settings['letter_spacing'] = array_key_exists('letterSpacing', $attributes) ? (float) $attributes['letterSpacing'] : (float) anchorkit_get_option('anchorkit_toc_letter_spacing', 0);
			$pro_settings['text_transform'] = array_key_exists('textTransform', $attributes) ? $attributes['textTransform'] : anchorkit_get_option('anchorkit_toc_text_transform', 'none');
			$pro_settings['link_underline'] = array_key_exists('linkUnderline', $attributes) ? $attributes['linkUnderline'] : anchorkit_get_option('anchorkit_toc_link_underline', 'none');
			$pro_settings['back_to_top_link'] = array_key_exists('backToTopLink', $attributes) ? (bool) $attributes['backToTopLink'] : $global_back_to_top_link;
			$pro_settings['back_to_top_text'] = isset($attributes['backToTopText']) && $attributes['backToTopText'] !== '' ? $attributes['backToTopText'] : $global_back_to_top_text;
			$pro_settings['back_to_top_font_size'] = isset($attributes['backToTopFontSize']) && $attributes['backToTopFontSize'] !== '' ? (int) $attributes['backToTopFontSize'] : $global_back_to_top_font_size;
			$pro_settings['schema_enabled'] = array_key_exists('schemaEnabled', $attributes) ? (bool) $attributes['schemaEnabled'] : null;
			$pro_settings['schema_type'] = isset($attributes['schemaType']) && $attributes['schemaType'] !== '' ? $attributes['schemaType'] : $schema_type_option;
		}
	}

	$settings = array_merge($settings, $pro_settings);

	$theme_preference = $effective_theme;

	// For preset mode (design_override disabled), Gutenberg blocks should use global styling
	// No override_tokens needed - they will use the global CSS variables
	// For custom styling mode (design_override enabled), override_tokens are set below

	// Pass container sizing and color/shadow overrides to generator when designOverride is enabled
	// AND there are actual custom values to apply
	$has_actual_design_overrides = false;
	if ($design_override) {
		$sanitize_hex = function ($v) {
			if (!is_string($v) || $v === '') {
				return '';
			}
			$v = ltrim($v, '#');
			$v = '#' . $v;
			return sanitize_hex_color($v) ?: '';
		};

		$light_tokens = array();
		$dark_tokens = array();

		$light_tokens['--anchorkit-toc-bg'] = $sanitize_hex($attributes['bgColorLight'] ?? '');
		$light_tokens['--anchorkit-toc-text-color'] = $sanitize_hex($attributes['textColorLight'] ?? '');
		$light_tokens['--anchorkit-toc-link-color'] = $sanitize_hex($attributes['linkColorLight'] ?? '');
		$light_tokens['--anchorkit-toc-link-hover-color'] = $sanitize_hex($attributes['linkHoverColorLight'] ?? '');
		$light_tokens['--anchorkit-toc-border-color'] = $sanitize_hex($attributes['borderColorLight'] ?? '');
		$light_tokens['--anchorkit-toc-bullet-color'] = $sanitize_hex($attributes['bulletColorLight'] ?? '');

		$dark_tokens['--anchorkit-toc-bg'] = $sanitize_hex($attributes['bgColorDark'] ?? '');
		$dark_tokens['--anchorkit-toc-text-color'] = $sanitize_hex($attributes['textColorDark'] ?? '');
		$dark_tokens['--anchorkit-toc-link-color'] = $sanitize_hex($attributes['linkColorDark'] ?? '');
		$dark_tokens['--anchorkit-toc-link-hover-color'] = $sanitize_hex($attributes['linkHoverColorDark'] ?? '');
		$dark_tokens['--anchorkit-toc-border-color'] = $sanitize_hex($attributes['borderColorDark'] ?? '');
		$dark_tokens['--anchorkit-toc-bullet-color'] = $sanitize_hex($attributes['bulletColorDark'] ?? '');

		$light_tokens = array_filter($light_tokens);
		$dark_tokens = array_filter($dark_tokens);

		if (!empty($light_tokens)) {
			$settings['override_tokens_light'] = $light_tokens;
			$has_actual_design_overrides = true;
		}
		if (!empty($dark_tokens)) {
			$settings['override_tokens_dark'] = $dark_tokens;
			$has_actual_design_overrides = true;
		}

		// Set active tokens for backward compatibility
		// For system theme, don't set override_tokens as inline styles to avoid conflicts with instance styles
		if ($effective_theme !== 'system') {
			$active_tokens = $light_tokens;
			if ($effective_theme === 'dark' && !empty($dark_tokens)) {
				$active_tokens = $dark_tokens;
			}
			if (!empty($active_tokens)) {
				$settings['override_tokens'] = $active_tokens;
			}
		}

		if (isset($attributes['containerPadding']) && $attributes['containerPadding'] !== null && $attributes['containerPadding'] !== '') {
			$settings['container_padding'] = intval($attributes['containerPadding']);
			$has_actual_design_overrides = true;
		}
		if (isset($attributes['containerBorderWidth']) && $attributes['containerBorderWidth'] !== null && $attributes['containerBorderWidth'] !== '') {
			$settings['container_border_width'] = intval($attributes['containerBorderWidth']);
			$has_actual_design_overrides = true;
		}
		if (isset($attributes['containerBorderRadius']) && $attributes['containerBorderRadius'] !== null && $attributes['containerBorderRadius'] !== '') {
			$settings['container_border_radius'] = intval($attributes['containerBorderRadius']);
			$has_actual_design_overrides = true;
		}
		if (!empty($attributes['boxShadowEnabled'])) {
			$has_actual_design_overrides = true;
			$hex_to_rgb = function ($hex) {
				$hex = trim($hex);
				if ($hex === '') {
					return array(0, 0, 0);
				}
				$hex = ltrim($hex, '#');
				if (strlen($hex) === 3) {
					$r = hexdec(str_repeat(substr($hex, 0, 1), 2));
					$g = hexdec(str_repeat(substr($hex, 1, 1), 2));
					$b = hexdec(str_repeat(substr($hex, 2, 1), 2));
					return array($r, $g, $b);
				}
				if (strlen($hex) === 6) {
					return array(hexdec(substr($hex, 0, 2)), hexdec(substr($hex, 2, 2)), hexdec(substr($hex, 4, 2)));
				}
				return array(0, 0, 0);
			};

			$light_rgb = $hex_to_rgb($attributes['shadowColorLight'] ?? '#000000');
			$light_opacity = isset($attributes['shadowOpacityLight']) ? floatval($attributes['shadowOpacityLight']) : 0.1;
			$settings['box_shadow_light'] =
				intval($attributes['shadowHOffsetLight'] ?? 0) . 'px ' .
				intval($attributes['shadowVOffsetLight'] ?? 4) . 'px ' .
				intval($attributes['shadowBlurLight'] ?? 6) . 'px ' .
				intval($attributes['shadowSpreadLight'] ?? 0) . 'px rgba(' .
				$light_rgb[0] . ', ' . $light_rgb[1] . ', ' . $light_rgb[2] . ', ' . max(0, min(1, $light_opacity)) . ')';

			$dark_rgb = $hex_to_rgb($attributes['shadowColorDark'] ?? '#000000');
			$dark_opacity = isset($attributes['shadowOpacityDark']) ? floatval($attributes['shadowOpacityDark']) : 0.3;
			$settings['box_shadow_dark'] =
				intval($attributes['shadowHOffsetDark'] ?? 0) . 'px ' .
				intval($attributes['shadowVOffsetDark'] ?? 2) . 'px ' .
				intval($attributes['shadowBlurDark'] ?? 8) . 'px ' .
				intval($attributes['shadowSpreadDark'] ?? 0) . 'px rgba(' .
				$dark_rgb[0] . ', ' . $dark_rgb[1] . ', ' . $dark_rgb[2] . ', ' . max(0, min(1, $dark_opacity)) . ')';
		}
	}

	// Set design_override when the user toggles "Customize Design" ON
	// This enables custom styling mode (same as auto-insert behavior)
	// User sees the global custom styling base, then can customize specific values
	if ($design_override) {
		$settings['design_override'] = true;
	}

	// Check if the generation function exists
	if (function_exists('anchorkit_generate_elementor_toc')) {
		$output_settings = $settings;
		$output_settings['return_parts'] = true;
		$parts = anchorkit_generate_elementor_toc($output_settings);

		// Check for empty output (no headings found)
		$is_empty = empty($parts) || (is_array($parts) && empty($parts['inner_html']));

		if ($is_empty && (is_admin() || (defined('REST_REQUEST') && REST_REQUEST))) {
			$heading_count = anchorkit_get_block_heading_count(
				$heading_levels_to_extract,
				$settings['exclude_selectors'],
				1, // min_depth - hardcoded to include all levels
				6, // max_depth - hardcoded to include all levels
				isset($attributes['excludeKeywords']) ? $attributes['excludeKeywords'] : '',
				isset($attributes['excludeRegex']) ? $attributes['excludeRegex'] : ''
			);

			if ($heading_count !== null && $heading_count < $min_headings_required) {
				$is_rendering = false;
				$heading_notice = sprintf(
					// translators: %1$d is the minimum heading count required, %2$d is the current heading count.
					esc_html__('Add at least %1$d headings (currently %2$d) for this Table of Contents to appear.', 'anchorkit-table-of-contents'),
					$min_headings_required,
					$heading_count
				);
				return '<div class="anchorkit-block-notice"><p>' . $heading_notice . '</p></div>';
			}
		}

		// Reconstruct HTML safely
		$toc_html = '';

		if (is_array($parts)) {
			// Late Escaping Implementation
			$container_tag = $parts['container_tag'];

			// Hooks output
			if (!empty($parts['before_html'])) {
				$toc_html .= $parts['before_html'];
			}

			// Build the container tag manually to correct escape attributes (preserving shadows in style)
			$toc_html .= '<' . $container_tag;
			foreach ($parts['container_attributes'] as $attr => $value) {
				$toc_html .= ' ' . esc_attr($attr) . '="' . esc_attr($value) . '"';
			}
			// Data/Extra attributes are pre-assembled strings in the generator
			if (!empty($parts['extra_attributes_string'])) {
				$toc_html .= ' ' . $parts['extra_attributes_string'];
			}
			$toc_html .= '>';

			// Trusted plugin HTML (title + toggle button) - bypass wp_kses to preserve button attributes/structure
			if (!empty($parts['header_html'])) {
				$toc_html .= $parts['header_html'];
			}

			// Strict content sanitization for inner content
			$toc_html .= wp_kses($parts['inner_html'], anchorkit_get_allowed_html_tags());

			// Trusted plugin HTML (View More/Back to Top) - bypass wp_kses
			if (!empty($parts['footer_html'])) {
				$toc_html .= $parts['footer_html'];
			}

			$toc_html .= '</' . $container_tag . '>';
		} else {
			// Fallback (e.g. empty string)
			$toc_html = $parts;
		}

		// Always wrap in block wrapper - the JS will handle sticky positioning on the wrapper
		// similar to how Elementor widgets are handled
		$is_sticky = isset($attributes['sticky']) && $attributes['sticky'];
		$sticky_position = isset($attributes['stickyPosition']) ? $attributes['stickyPosition'] : 'content';
		$is_sticky_any = $is_sticky && in_array($sticky_position, array('content', 'left', 'right'), true);

		$is_rendering = false;
		if (function_exists('get_block_wrapper_attributes')) {
			// Add Gutenberg-specific class, and sticky class if sticky is enabled
			$extra_classes = 'anchorkit-gutenberg-block-wrapper';
			if ($is_sticky_any) {
				$extra_classes .= ' anchorkit-gutenberg-block-sticky';
			}
			// get_block_wrapper_attributes returns a string of attributes, incorrectly escaped by WP core itself
			return '<div ' . get_block_wrapper_attributes(array('class' => $extra_classes)) . '>' . $toc_html . '</div>';
		}
		return $toc_html;
	}

	// Fallback to old method if function doesn't exist
	global $post;
	if (!$post) {
		$is_rendering = false;
		return '';
	}

	$content_copy = $post->post_content;
	$parsed = anchorkit_toc_parse_headings(
		$content_copy,
		$settings['heading_levels'],
		$settings['exclude_selectors']
	);

	if (count($parsed['headings']) < $min_headings_required) {
		// Show a helpful message in the editor
		$is_rendering = false;
		if (is_admin() || (defined('REST_REQUEST') && REST_REQUEST)) {
			return '<div class="anchorkit-block-notice">
                <p><strong>AnchorKit:</strong> Not enough headings found. Add at least ' . esc_html($min_headings_required) . ' headings to display the Table of Contents.</p>
            </div>';
		}
		return '';
	}

	$context = array(
		'source' => 'gutenberg_fallback',
		'post_id' => ($post && isset($post->ID)) ? (int) $post->ID : 0,
		'data' => array(
			'block_settings' => $settings,
		),
	);

	$is_rendering = false;
	return anchorkit_toc_generate_html($parsed['headings'], $parsed['content'], $context);
}

/**
 * Count how many headings would qualify for the TOC given current controls.
 *
 * @param array  $heading_levels
 * @param string $exclude_selectors
 * @param int    $min_depth
 * @param int    $max_depth
 * @param string $exclude_keywords
 * @param string $exclude_regex
 * @return int|null
 */
function anchorkit_get_block_heading_count($heading_levels, $exclude_selectors, $min_depth, $max_depth, $exclude_keywords, $exclude_regex)
{
	if (!function_exists('anchorkit_toc_parse_headings')) {
		return null;
	}

	global $post;
	if (!$post) {
		return null;
	}

	$content_copy = $post->post_content;
	$parsed = anchorkit_toc_parse_headings($content_copy, $heading_levels, $exclude_selectors);
	$headings = $parsed['headings'];

	if (!empty($exclude_keywords)) {
		$keywords = array_filter(array_map('trim', explode(',', $exclude_keywords)));
		if (!empty($keywords)) {
			$headings = array_values(
				array_filter(
					$headings,
					function ($heading) use ($keywords) {
						foreach ($keywords as $keyword) {
							if ($keyword !== '' && stripos($heading['text'], $keyword) !== false) {
								return false;
							}
						}
						return true;
					}
				)
			);
		}
	}

	// exclude_regex is used for trimming prefixes, not for excluding headings from the count.
	// So we do NOT filter headings based on exclude_regex here.
	/*
	if (!empty($exclude_regex)) {
		$pattern = anchorkit_normalize_heading_exclude_regex($exclude_regex);
		if ($pattern !== '' && @preg_match($pattern, '') !== false) {
			$headings = array_values(array_filter($headings, function ($heading) use ($pattern) {
				return @preg_match($pattern, $heading['text']) !== 1;
			}));
		}
	}
	*/

	$min_depth = max(1, (int) $min_depth);
	$max_depth = max($min_depth, (int) $max_depth);
	$headings = array_values(
		array_filter(
			$headings,
			function ($heading) use ($min_depth, $max_depth) {
				$level = is_numeric($heading['level']) ? (int) $heading['level'] : (int) str_replace('h', '', $heading['level']);
				return $level >= $min_depth && $level <= $max_depth;
			}
		)
	);

	return count($headings);
}

/**
 * Generate a preview for the block editor
 *
 * @param array $attributes Block attributes
 * @return string Preview HTML
 */
function anchorkit_toc_block_preview($attributes = array())
{
	$attributes['preview'] = false;
	return anchorkit_render_toc_block($attributes);
}



/**
 * Enqueue block editor assets
 */
function anchorkit_enqueue_block_editor_assets()
{
	$block_defaults = function_exists('anchorkit_get_toc_default_settings')
		? anchorkit_get_toc_default_settings()
		: array();

	// Enqueue block JavaScript - using simplified version for better compatibility
	wp_enqueue_script(
		'anchorkit-toc-block',
		plugins_url('js/blocks/toc-block-simple.js', ANCHORKIT_PLUGIN_FILE),
		array('wp-blocks', 'wp-element', 'wp-editor', 'wp-block-editor', 'wp-components', 'wp-i18n'),
		ANCHORKIT_PLUGIN_VERSION,
		true
	);

	// Determine if we are in pro mode for the block editor
	$can_use_premium = false;
	if (anchorkit_fs() && anchorkit_fs()->is__premium_only()) {
		if (anchorkit_fs()->can_use_premium_code()) {
			$can_use_premium = true;
		}
	}

	// Pass settings to the block
	wp_localize_script(
		'anchorkit-toc-block',
		'anchorkitBlockSettings',
		array(
			'isEnabled' => (bool) anchorkit_get_option('anchorkit_toc_enabled', false),
			'settingsUrl' => admin_url('admin.php?page=anchorkit'),
			'isPro' => $can_use_premium,
			'checkoutUrl' => anchorkit_get_checkout_url(),
			'defaults' => $block_defaults,
		)
	);

	// Enqueue block editor styles
	wp_enqueue_style(
		'anchorkit-block-editor',
		plugins_url('css/block-editor.css', ANCHORKIT_PLUGIN_FILE),
		array('wp-edit-blocks'),
		ANCHORKIT_PLUGIN_VERSION
	);

	// Enqueue frontend styles for the preview
	wp_enqueue_style(
		'anchorkit-toc-css',
		plugins_url('css/anchorkit-frontend.css', ANCHORKIT_PLUGIN_FILE),
		array(),
		ANCHORKIT_PLUGIN_VERSION
	);

	// Mirror frontend CSS variables/token output inside the block editor
	if (function_exists('anchorkit_get_toc_inline_css')) {
		$toc_inline_styles = anchorkit_get_toc_inline_css();

		if (!empty($toc_inline_styles)) {
			wp_add_inline_style('anchorkit-toc-css', $toc_inline_styles);
		}
	}

	// Enqueue frontend JavaScript for View More functionality in preview
	// Load in header for block editor to ensure it's available when React initializes
	wp_enqueue_script(
		'anchorkit-toc-js',
		plugins_url('js/table-of-contents.js', ANCHORKIT_PLUGIN_FILE),
		array('jquery'),
		ANCHORKIT_PLUGIN_VERSION,
		false  // Load in header for editor context
	);
}
add_action('enqueue_block_editor_assets', 'anchorkit_enqueue_block_editor_assets');

/**
 * Add block category for AnchorKit blocks
 */
function anchorkit_block_categories($categories, $post = null)
{
	return array_merge(
		$categories,
		array(
			array(
				'slug' => 'anchorkit',
				'title' => __('AnchorKit', 'anchorkit-table-of-contents'),
				'icon' => 'list-view',
			),
		)
	);
}

// Support both old and new filter names for WordPress compatibility
if (version_compare($GLOBALS['wp_version'], '5.8', '>=')) {
	add_filter('block_categories_all', 'anchorkit_block_categories', 10, 2);
} else {
	add_filter('block_categories', 'anchorkit_block_categories', 10, 2);
}

/**
 * Register custom REST API endpoint for block preview
 * This bypasses WordPress's strict block renderer validation
 */
function anchorkit_register_block_preview_endpoint()
{
	register_rest_route(
		'anchorkit/v1',
		'/block-preview',
		array(
			'methods' => 'POST',
			'callback' => 'anchorkit_block_preview_callback',
			'permission_callback' => function () {
				return current_user_can('edit_posts');
			},
			'args' => array(
				'attributes' => array(
					'required' => true,
					'type' => 'object',
					'sanitize_callback' => function ($value) {
						$value = is_array($value) ? $value : array();
						return anchorkit_sanitize_block_attributes($value);
					},
				),
				'post_id' => array(
					'required' => false,
					'type' => 'integer',
					'sanitize_callback' => 'absint',
				),
				'content' => array(
					'required' => false,
					'type' => 'string',
					'sanitize_callback' => 'wp_kses_post',
				),
			),
		)
	);
}
add_action('rest_api_init', 'anchorkit_register_block_preview_endpoint');

/**
 * REST API callback for block preview
 */
function anchorkit_block_preview_callback($request)
{
	$attributes = $request->get_param('attributes');
	$attributes = anchorkit_sanitize_block_attributes(is_array($attributes) ? $attributes : array());
	$post_id = $request->get_param('post_id');
	$content = $request->get_param('content');

	// Set global post if post_id is provided
	if ($post_id) {
		global $post;
		$post = get_post($post_id);
		setup_postdata($post);

		// If live content is provided from editor, use it instead of saved content
		// This enables live preview of unsaved changes in the block editor
		if ($post && !empty($content)) {
			$post->post_content = $content;
		}
	}

	// Ensure preview flag is set
	if (!isset($attributes['preview'])) {
		$attributes['preview'] = true;
	}

	// Generate preview HTML
	$html = anchorkit_toc_block_preview($attributes);

	// Reset post data if we set it
	if ($post_id) {
		wp_reset_postdata();
	}

	return new WP_REST_Response(
		array(
			'rendered' => $html,
		),
		200
	);
}
