<?php
namespace Bricks\Integrations;

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

class Block_Editor {
	public function __construct() {
		add_action( 'init', [ $this, 'register_blocks' ] );
		add_shortcode( 'bricks_component', [ $this, 'render_component_shortcode' ] );
	}

	/**
	 * Register all component blocks
	 */
	public function register_blocks() {
		if ( ! function_exists( 'register_block_type' ) ) {
			return;
		}

		$this->register_bricks_components_as_blocks();
	}

	/**
	 * Register all components as blocks, if enabled for block editor
	 */
	public function register_bricks_components_as_blocks() {
		if ( ! \Bricks\Database::get_setting( 'bricksComponentsInBlockEditor' ) ) {
			return;
		}

		$components = get_option( BRICKS_DB_COMPONENTS, [] );

		foreach ( $components as $component ) {
			if ( empty( $component['id'] ) || empty( $component['elements'] ) ) {
				continue;
			}

			// NOTE: Register ALL components as blocks (not just enabled ones)
			// The JavaScript side will handle showing placeholders for disabled components
			$this->register_component_block( $component );
		}
	}

	/**
	 * Register a single component block
	 *
	 * @param array $component Component data.
	 * @return void
	 */
	public function register_component_block( $component ) {
		// Skip if no data
		if ( ! $component || empty( $component['elements'] ) ) {
			return;
		}

		// Use component ID directly for block name (matches JavaScript registration)
		$block_name = 'bricks-components/' . $component['id'];

		// Get component name from the first element or use ID
		$component_name = '';
		if ( isset( $component['elements'][0]['label'] ) ) {
			$component_name = $component['elements'][0]['label'];
		} else {
			$component_name = sprintf(
				/* translators: %s: Component ID */
				__( 'Component %s', 'bricks' ),
				$component['id']
			);
		}

		$attributes = [
			'componentId' => [
				'type'    => 'string',
				'default' => $component['id'],
			],
			'properties'  => [
				'type'    => 'object',
				'default' => [],
			],
			'blockId'     => [
				'type'    => 'string',
				'default' => '',
			],
			'variant'     => [
				'type'    => 'string',
				'default' => '',
			],
			'_preview'    => [
				'type'    => 'boolean',
				'default' => false,
			],
		];

		// Register block type
		register_block_type(
			$block_name,
			[
				'attributes'      => $attributes,
				'render_callback' => [ $this, 'render_component_block' ],
				'category'        => $component['blockCategory'] ?? 'bricks',
				'supports'        => [
					'align' => [ 'wide', 'full' ],
				],
			]
		);
	}

	/**
	 * Render component block
	 *
	 * @param array $attributes Block attributes.
	 * @return string Rendered HTML.
	 */
	public function render_component_block( $attributes ) {
		try {
			$component_id = $attributes['componentId'] ?? '';

			if ( ! $component_id ) {
				return '';
			}

			// Check if component is enabled for block editor before rendering
			$components = get_option( BRICKS_DB_COMPONENTS, [] );
			$component  = null;

			foreach ( $components as $comp ) {
				if ( isset( $comp['id'] ) && $comp['id'] === $component_id ) {
					$component = $comp;
					break;
				}
			}

			// Return empty string if component not found or not enabled for block editor
			if ( ! $component ) {
				return '';
			}

			// Check if component is enabled for block editor
			if ( \Bricks\Database::get_setting( 'bricksComponentsInBlockEditor' ) === 'manual' && empty( $component['blockEditor'] ) ) {
				return '';
			}

			// Translate attributes if WPML is active
			if ( \Bricks\Integrations\Wpml\Wpml::is_wpml_active() ) {
				$attributes = \Bricks\Integrations\Wpml\Wpml::translate_component_block_attributes( $attributes, get_the_ID() );
			}

			// Render component directly with attributes
			$content = $this->render_component_shortcode( $attributes );

			if ( ! $content ) {
				return '';
			}

			// Apply alignment class wrapper
			if ( ! empty( $attributes['align'] ) ) {
				return '<div class="align' . esc_attr( $attributes['align'] ) . '">' . $content . '</div>';
			}

			return $content;
		} catch ( \Exception $e ) {
			return '';
		}
	}

	/**
	 * Render component shortcode: [bricks_component id="component_id"]
	 *
	 * Simplified version that leverages Bricks' native component system
	 */
	public function render_component_shortcode( $attributes = [] ) {
		try {
			// Handle both direct calls (from blocks) and shortcode calls
			$component_id = ! empty( $attributes['componentId'] ) ? sanitize_text_field( $attributes['componentId'] ) :
							( ! empty( $attributes['id'] ) ? sanitize_text_field( $attributes['id'] ) : false );

			if ( ! $component_id ) {
				return '';
			}

			// Check if component exists
			$component = \Bricks\Helpers::get_component_by_cid( $component_id );
			if ( ! $component ) {
				return '';
			}

			// Check if component is enabled for block editor
			if ( \Bricks\Database::get_setting( 'bricksComponentsInBlockEditor' ) === 'manual' && empty( $component['blockEditor'] ) ) {
				return '';
			}

			// Get properties from attributes (handle both new and legacy formats)
			$properties = [];
			if ( isset( $attributes['properties'] ) && is_array( $attributes['properties'] ) ) {
				// New format: single properties object
				$properties = $attributes['properties'];
			}

			// Get block ID for unique element ID
			$block_id = ! empty( $attributes['blockId'] ) ? sanitize_text_field( $attributes['blockId'] ) : '';

			// Get variant
			$variant = ! empty( $attributes['variant'] ) ? sanitize_text_field( $attributes['variant'] ) : '';

			// Get the main element from the component
			$main_element = null;
			foreach ( $component['elements'] as $element ) {
				if ( $element['id'] === $component_id ) {
					$main_element = $element;
					break;
				}
			}

			if ( ! $main_element ) {
				return '';
			}

			// Create component element using the main element's name and structure
			// Use blockId for consistent element ID, fallback to component ID if no blockId
			$element_id = $block_id ? $component_id . '-' . $block_id : $component_id;

			$component_element = [
				'id'         => $element_id,
				'name'       => $main_element['name'], // Use the actual element name (e.g., 'post - title')
				'cid'        => $component_id,
				'properties' => $properties,
			];

			// Add variant if specified
			if ( $variant ) {
				$component_element['variant'] = $variant;
			}

			// Generate CSS for this component instance
			\Bricks\Assets::generate_css_from_elements( [ $component_element ], "component_$component_id" );

			// Prepare all settings into enqueue_setting_specific_scripts
			$all_elements       = [];
			$component_instance = \Bricks\Helpers::get_component_instance( $component_element );

			if ( ! empty( $component_instance ) ) {
				// Get all nested elements for this component instance (#86c7ac7wk; @since 2.2)
				\Bricks\Helpers::get_component_elements_recursive( $component_instance, $all_elements );
			} else {
				$all_elements = [ $component_element ];
			}

			// Enqueue icon fonts and other setting-specific scripts for this component instance
			\Bricks\Assets::enqueue_setting_specific_scripts( $all_elements );

			// Ensure theme styles are loaded for Gutenberg context
			if ( $this->is_gutenberg_render() && empty( \Bricks\Theme_Styles::$settings_by_id ) ) {
				\Bricks\Theme_Styles::load_set_styles();
			}

			// Handle CSS output based on context
			$html = '';
			if ( bricks_is_builder() || bricks_is_builder_call() || $this->is_gutenberg_render() ) {
				// For builder/Gutenberg: Add inline CSS for immediate preview
				$component_css = \Bricks\Assets::$inline_css[ "component_$component_id" ] ?? '';

				// Add global styles for editor contexts
				$global_css         = '';
				$global_classes_css = \Bricks\Assets::generate_global_classes();
				if ( $global_classes_css ) {
					$global_css .= "\n/* Global Classes */\n" . $global_classes_css;
				}

				$global_variables = \Bricks\Assets::get_global_variables();
				if ( $global_variables ) {
					$variables_css = \Bricks\Assets::format_variables_as_css( $global_variables );
					if ( $variables_css ) {
						$global_css .= "\n/* Global Variables */\n" . $variables_css;
					}
				}

				$global_colors = \Bricks\Assets::generate_inline_css_color_vars( \Bricks\Database::$global_data['colorPalette'] );
				if ( $global_colors ) {
					$global_css .= "\n/* Global Colors */\n" . $global_colors;
				}

				// Add theme styles that apply to current page
				$theme_style_css = '';
				if ( ! empty( \Bricks\Theme_Styles::$settings_by_id ) ) {
					foreach ( \Bricks\Theme_Styles::$settings_by_id as $style_id => $settings ) {
						$theme_style_css .= \Bricks\Assets::generate_inline_css_theme_style( $settings );
					}
				}
				if ( $theme_style_css ) {
					$global_css .= "\n/* Theme Styles */\n" . $theme_style_css;
				}

				// Disable links in editor
				$editor_css    = "\n/* Disable links in editor */\n.brxe-{$component_id} a { pointer-events: none; }\n";
				$webfont_links = '';

				// Scope CSS to Gutenberg editor canvas
				if ( $this->is_gutenberg_render() ) {
					$all_css = $global_css . $component_css . $editor_css;

					// Add webfonts
					$webfont_links = $component_css ? \Bricks\Assets::load_webfonts( $component_css, true ) : '';

					if ( $all_css ) {
						$scoped_css = self::scope_css_for_gutenberg( $all_css );
						$html      .= "{$webfont_links}<style id=\"bricks-inline-css-component-{$component_id}\">{$scoped_css}</style>";
					} else {
						$html .= $webfont_links;
					}
				} else {
					if ( $component_css ) {
						\Bricks\Assets::load_webfonts( $component_css );
					}

					$html .= "{$webfont_links}<style id=\"bricks-inline-css-component-{$component_id}\">{$global_css}{$component_css}{$editor_css}</style>";
				}
			} else {
				// For frontend: Add CSS to Bricks' normal CSS handling system
				$component_css = \Bricks\Assets::$inline_css[ "component_$component_id" ] ?? '';
				if ( $component_css ) {
					// Add to dynamic CSS for frontend output
					\Bricks\Assets::$inline_css_dynamic_data .= $component_css;

					// Load webfonts for frontend
					\Bricks\Assets::load_webfonts( $component_css );
				}
			}

			// Prevent infinite loops
			static $rendered_components = [];
			if ( in_array( $component_id, $rendered_components, true ) ) {
				return '';
			}

			$rendered_components[] = $component_id;

			// Let Bricks handle everything - this is the key simplification!
			// But first, ensure we have post context for post-related elements
			global $post;
			$original_post = $post;

			// If no post context in Gutenberg, try to get the current editing post
			if ( ! $post && $this->is_gutenberg_render() ) {
				$post_id = get_the_ID();
				if ( ! $post_id && isset( $_GET['post'] ) ) {
					$post_id = intval( $_GET['post'] );
				}
				if ( ! $post_id && isset( $_POST['post_id'] ) ) {
					$post_id = intval( $_POST['post_id'] );
				}

				if ( $post_id ) {
					$post = get_post( $post_id );
					setup_postdata( $post );
				}
			}

			// Add parent component to Frontend::$elements so nested components can resolve parent properties
			// See: Helpers::resolve_parent_property_value()
			\Bricks\Frontend::$elements[ $element_id ] = $component_element;

			$html .= \Bricks\Frontend::render_element( $component_element );

			// Restore original post context
			if ( $original_post ) {
				$post = $original_post;
				setup_postdata( $post );
			} elseif ( ! $original_post && $post ) {
				wp_reset_postdata();
			}

			array_pop( $rendered_components );

			return $html;

		} catch ( \Exception $e ) {
			return '';
		}
	}

	/**
	 * Scope CSS for Gutenberg editor while preserving root-level declarations.
	 *
	 * Keeps @font-face and @keyframes at root level and scopes regular rules to the editor iframe body.
	 *
	 * @param string $css CSS to scope.
	 * @return string Scoped CSS.
	 */
	public static function scope_css_for_gutenberg( $css ) {
		if ( empty( $css ) ) {
			return $css;
		}

		$root_level_css = '';

		// STEP: Extract @font-face blocks
		if ( preg_match_all( '/@font-face\s*\{[^}]*\}/s', $css, $font_matches ) ) {
			foreach ( $font_matches[0] as $font_block ) {
				$root_level_css .= $font_block . "\n";
				$css             = str_replace( $font_block, '', $css );
			}
		}

		// STEP: Extract @keyframes blocks
		if ( preg_match_all( '/@(?:-webkit-)?keyframes\s+[^{]+\{(?:[^{}]*\{[^}]*\})*[^}]*\}/s', $css, $keyframe_matches ) ) {
			foreach ( $keyframe_matches[0] as $keyframe_block ) {
				$root_level_css .= $keyframe_block . "\n";
				$css             = str_replace( $keyframe_block, '', $css );
			}
		}

		return $root_level_css . self::scope_css_rules_for_gutenberg( $css );
	}

	/**
	 * Scope regular CSS rules to the block editor canvas.
	 *
	 * @param string $css CSS to scope.
	 * @return string
	 */
	private static function scope_css_rules_for_gutenberg( $css ) {
		$scoped_css = '';
		$offset     = 0;
		$length     = strlen( $css );

		while ( $offset < $length ) {
			$open_position = strpos( $css, '{', $offset );

			if ( $open_position === false ) {
				$scoped_css .= substr( $css, $offset );
				break;
			}

			$selector = substr( $css, $offset, $open_position - $offset );
			$close    = self::find_matching_css_brace( $css, $open_position );

			if ( $close === false ) {
				$scoped_css .= substr( $css, $offset );
				break;
			}

			$body   = substr( $css, $open_position + 1, $close - $open_position - 1 );
			$prefix = '';

			if ( preg_match( '/^(\\s*(?:\\/\\*.*?\\*\\/\\s*)*)(.*?)$/s', $selector, $matches ) ) {
				$prefix   = $matches[1];
				$selector = $matches[2];
			}

			$selector = trim( $selector );

			if ( $selector === '' ) {
				$scoped_css .= $prefix . '{' . $body . '}';
			} elseif ( strpos( $selector, '@' ) === 0 ) {
				$scoped_css .= $prefix . $selector . '{' . self::scope_css_at_rule_body_for_gutenberg( $selector, $body ) . '}';
			} else {
				$scoped_css .= $prefix . self::scope_css_selector_list_for_gutenberg( $selector ) . '{' . $body . '}';
			}

			$offset = $close + 1;
		}

		return $scoped_css;
	}

	/**
	 * Scope at-rule contents when they contain regular style rules.
	 *
	 * @param string $selector At-rule selector.
	 * @param string $body At-rule body.
	 * @return string
	 */
	private static function scope_css_at_rule_body_for_gutenberg( $selector, $body ) {
		if ( preg_match( '/^@(media|supports|container|layer)\\b/i', $selector ) ) {
			return self::scope_css_rules_for_gutenberg( $body );
		}

		return $body;
	}

	/**
	 * Find the matching closing brace for a CSS block.
	 *
	 * @param string $css CSS string.
	 * @param int    $open_position Opening brace position.
	 * @return int|false
	 */
	private static function find_matching_css_brace( $css, $open_position ) {
		$depth  = 0;
		$length = strlen( $css );

		for ( $i = $open_position; $i < $length; $i++ ) {
			if ( $css[ $i ] === '{' ) {
				$depth++;
			} elseif ( $css[ $i ] === '}' ) {
				$depth--;

				if ( $depth === 0 ) {
					return $i;
				}
			}
		}

		return false;
	}

	/**
	 * Scope a comma-separated selector list to the block editor canvas.
	 *
	 * @param string $selector_list CSS selector list.
	 * @return string
	 */
	private static function scope_css_selector_list_for_gutenberg( $selector_list ) {
		$selectors = self::split_css_selector_list( $selector_list );
		$scoped    = [];

		foreach ( $selectors as $selector ) {
			$scoped[] = self::scope_css_selector_for_gutenberg( $selector );
		}

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

	/**
	 * Split a selector list while respecting functional pseudo selectors.
	 *
	 * @param string $selector_list CSS selector list.
	 * @return array
	 */
	private static function split_css_selector_list( $selector_list ) {
		$selectors     = [];
		$current       = '';
		$paren_depth   = 0;
		$bracket_depth = 0;
		$length        = strlen( $selector_list );

		for ( $i = 0; $i < $length; $i++ ) {
			$char = $selector_list[ $i ];

			if ( $char === '(' ) {
				$paren_depth++;
			} elseif ( $char === ')' ) {
				$paren_depth = max( 0, $paren_depth - 1 );
			} elseif ( $char === '[' ) {
				$bracket_depth++;
			} elseif ( $char === ']' ) {
				$bracket_depth = max( 0, $bracket_depth - 1 );
			}

			if ( $char === ',' && $paren_depth === 0 && $bracket_depth === 0 ) {
				$selectors[] = trim( $current );
				$current     = '';
				continue;
			}

			$current .= $char;
		}

		if ( trim( $current ) !== '' ) {
			$selectors[] = trim( $current );
		}

		return $selectors;
	}

	/**
	 * Scope one selector to the block editor canvas.
	 *
	 * @param string $selector CSS selector.
	 * @return string
	 */
	private static function scope_css_selector_for_gutenberg( $selector ) {
		$selector = trim( $selector );

		if ( $selector === '' ) {
			return $selector;
		}

		if (
			strpos( $selector, '.editor-styles-wrapper' ) === 0 ||
			strpos( $selector, '.is-root-container' ) === 0 ||
			strpos( $selector, '.block-editor-iframe__body' ) === 0
		) {
			return $selector;
		}

		if ( strpos( $selector, ':root' ) === 0 ) {
			return self::scope_css_selector_to_editor_wrapper( substr( $selector, 5 ) );
		}

		if ( preg_match( '/^body([^\\s>+~]*)(.*)$/', $selector, $matches ) ) {
			return self::scope_css_selector_to_editor_wrapper( $matches[1] . $matches[2] );
		}

		if ( preg_match( '/^html(?:[^\\s>+~]*)?(.*)$/', $selector, $matches ) ) {
			return self::scope_css_selector_to_editor_wrapper( $matches[1] );
		}

		return self::scope_css_selector_to_editor_wrapper( ' ' . $selector );
	}

	/**
	 * Return the canonical block editor canvas selector.
	 *
	 * @param string $suffix Selector suffix.
	 * @return string
	 */
	private static function scope_css_selector_to_editor_wrapper( $suffix = '' ) {
		return '.editor-styles-wrapper' . $suffix;
	}

	/**
	 * Check if we're in a Gutenberg ServerSideRender context
	 */
	private function is_gutenberg_render() {
		// Check if we're in a REST API call for block rendering
		if ( defined( 'REST_REQUEST' ) && REST_REQUEST ) {
			return true;
		}

		// Check for AJAX request from Gutenberg block renderer
		if ( defined( 'DOING_AJAX' ) && DOING_AJAX ) {
			if ( isset( $_POST['action'] ) && $_POST['action'] === 'gutenberg_render_block' ) {
				return true;
			}
		}

		// Check if we're in admin and not in Bricks builder
		if ( is_admin() && ! bricks_is_builder() && ! bricks_is_builder_call() ) {
			return true;
		}

		// Check for specific Gutenberg query parameters
		if ( isset( $_GET['context'] ) && sanitize_text_field( wp_unslash( $_GET['context'] ) ) === 'edit' ) {
			return true;
		}

		// Check if current screen is Gutenberg editor
		if ( function_exists( 'get_current_screen' ) ) {
			$screen = get_current_screen();
			if ( $screen && method_exists( $screen, 'is_block_editor' ) && $screen->is_block_editor() ) {
				return true;
			}
		}

		// Check for block editor specific headers or request attributes
		if ( isset( $_SERVER['HTTP_X_WP_NONCE'] ) && is_admin() ) {
			return true;
		}

		return false;
	}

	/**
	 * Get select options from the first connected element with a select control
	 *
	 * @param array $property    The component property array.
	 * @param array $elements    The component elements array.
	 * @return array|null The select options array or null if not found.
	 */
	public function get_select_options_from_connected_elements( $property, $elements ) {
		// Only process select properties that have connections
		if ( $property['type'] !== 'select' || empty( $property['connections'] ) || ! is_array( $property['connections'] ) ) {
			return null;
		}

		// Check each connected element
		foreach ( $property['connections'] as $element_id => $connection_paths ) {
			// Find the element in the elements array
			$element = $this->find_element_by_id( $elements, $element_id );
			if ( ! $element ) {
				continue;
			}

			// Get the element's controls to find select controls
			$element_controls = \Bricks\Elements::get_element( $element, 'controls' );
			if ( empty( $element_controls ) ) {
				continue;
			}

			// Check each connection path to find select controls
			foreach ( $connection_paths as $path ) {
				// For simple paths (most common case)
				if ( strpos( $path, '.' ) === false ) {
					if ( isset( $element_controls[ $path ] ) ) {
						$control = $element_controls[ $path ];
						if ( isset( $control['type'] ) && $control['type'] === 'select' && ! empty( $control['options'] ) ) {
							return $control['options'];
						}
					}
				}
			}
		}

		return null;
	}

	/**
	 * Find element by ID in elements array (recursive)
	 *
	 * @param array  $elements   The elements array to search.
	 * @param string $element_id The element ID to find.
	 * @return array|null The element array or null if not found.
	 */
	private function find_element_by_id( $elements, $element_id ) {
		foreach ( $elements as $element ) {
			if ( isset( $element['id'] ) && $element['id'] === $element_id ) {
				return $element;
			}

			// Search recursively in children
			if ( isset( $element['children'] ) && is_array( $element['children'] ) ) {
				$found = $this->find_element_by_id( $element['children'], $element_id );
				if ( $found ) {
					return $found;
				}
			}
		}

		return null;
	}

}
