<?php
namespace Crocoblock\Blocks_Style;

/**
 * Block handler class
 *
 * @since 1.0.0
 */
class Block {

	/**
	 * Block name
	 *
	 * @var string
	 */
	protected $name;

	/**
	 * Block attributes
	 *
	 * @var array
	 */
	protected $attributes;

	/**
	 * Block styles
	 *
	 * @var array
	 */
	protected $styles;

	/**
	 * Block controls stack
	 *
	 * @var array
	 */
	protected $controls_stack = array(
		'children' => array(),
	);

	/**
	 * Whether the block is rendered via REST API.
	 *
	 * @var bool
	 */
	protected $is_rest_render = false;

	/**
	 * Currently processed section for registration.
	 */
	protected $_current_section = null;

	/**
	 * Currently processed tabs for registration.
	 */
	protected $_current_tabs = null;

	/**
	 * Currently processed tab for registration.
	 */
	protected $_current_tab = null;

	protected $css_props = array();

	protected $defaults = null;

	/**
	 * Constructor
	 *
	 * @param string $name Block name.
	 * @param array  $attributes Block attributes.
	 */
	public function __construct( $name, $attributes = array() ) {
		$this->name       = $name;
		$this->attributes = $attributes;

		add_filter(
			'rest_request_before_callbacks',
			array( $this, 'prevent_styles_on_self_render' ),
			10, 3
		);

		add_filter(
			'render_block_' . $this->get_block_name(),
			array( $this, 'render_block_styles' ),
			10, 3
		);
	}

	/**
	 * Prevent styles from being rendered when there is a Rest API request
	 * to render the block itself.
	 *
	 * @param mixed $response The response object.
	 * @param mixed $handler The handler for the request.
	 * @param WP_REST_Request $request The request object.
	 * @return mixed
	 */
	public function prevent_styles_on_self_render( $response, $handler, $request ) {

		if ( strpos( $request->get_route(), '/wp/v2/block-renderer/' ) === 0 ) {
			$block_name = $request->get_param( 'name' );

			if ( ! $block_name ) {
				$route_parts = explode( '/', trim( $request->get_route(), '/' ) );
				$block_name = end( $route_parts );
			}

			if ( $block_name === $this->get_block_name() ) {
				/*remove_filter(
					'render_block_' . $this->get_block_name(),
					array( $this, 'render_block_styles' ),
					10, 3
				);*/
				$this->is_rest_render = true;
			}
		}

		return $response;
	}

	/**
	 * Render block styles
	 *
	 * @param string $block_content Block content.
	 * @param array  $block Block data.
	 * @param bool   $is_preview Is preview mode.
	 * @return string
	 */
	public function render_block_styles( $block_content, $parsed_block, $wp_block ) {

		if ( ! empty( $parsed_block['attrs'][ Registry::instance()->get_support_name() ] ) ) {

			if ( $this->is_rest_render ) {

				$block_class_name = Style_Engine::get_classname_from_attrs(
					$parsed_block['attrs'][ Registry::instance()->get_support_name() ]
				);

				$style_inserter = new Style_Inserter( $block_class_name, [] );

				return $style_inserter->with_class_name( $block_content );
			}

			$style_engine = new Style_Engine(
				$parsed_block['attrs'][ Registry::instance()->get_support_name() ],
				$this->css_props
			);

			$styles_data = Style_Cache::get_instance()->get_cached( $style_engine );

			$style_inserter = new Style_Inserter(
				$style_engine->get_class_name(),
				$styles_data
			);

			$block_content = $style_inserter->insert_styles( $block_content );

			if ( ! apply_filters( 'jet-styles-manager/block/force-print-styles', false, $this ) ) {
				Style_Cache::get_instance()->add_printed( $style_engine->get_class_name() );
			}
		}

		return $block_content;
	}

	/**
	 * Get block name
	 *
	 * @return string
	 */
	public function get_block_name() {
		return $this->name;
	}

	/**
	 * Get currently registered controls.
	 *
	 * @return array
	 */
	public function get_controls_stack() {
		return $this->controls_stack['children'];
	}

	/**
	 * Get default values for the registered controls,
	 * if there were any.
	 *
	 * @return array|null
	 */
	public function get_defaults() {
		return $this->defaults;
	}

	/**
	 * Whether to register full controls or only CSS-render-related.
	 *
	 * @return bool
	 */
	public function should_register_full_controls() {
		return is_admin();
	}

	/**
	 * Start a new section for block styles
	 *
	 * @param array $args
	 * @return void
	 */
	public function start_section( $args = array() ) {

		if ( ! $this->should_register_full_controls() ) {
			return;
		}

		if ( ! isset( $args['id'] ) ) {
			_doing_it_wrong(
				'Blocks_Style\Proxy::start_section',
				'Section id is required.',
				'1.0.0'
			);
			return;
		}

		$section_id = $args['id'];

		$this->_current_section = $section_id;

		if ( ! isset( $this->controls_stack['children'][ $this->_current_section ] ) ) {
			$this->controls_stack['children'][ $this->_current_section ] = $args;
			$this->controls_stack['children'][ $this->_current_section ]['children'] = array();
		}
	}

	/**
	 * End the current section
	 *
	 * @return void
	 */
	public function end_section() {

		if ( ! $this->should_register_full_controls() ) {
			return;
		}

		$this->_current_section = null;
	}

	/**
	 * Add a new control to the current section
	 *
	 * @param array $args
	 * @return void
	 */
	public function add_control( $args = array() ) {

		if ( ! $this->should_register_full_controls() ) {

			$control_id = ! empty( $args['id'] ) ? $args['id'] : false;

			$this->css_props[ $control_id ] = array(
				'type'         => ! empty( $args['type'] ) ? $args['type'] : 'text',
				'css_var'      => ! empty( $args['css_var'] ) ? $args['css_var'] : array(),
				'css_selector' => ! empty( $args['css_selector'] ) ? $args['css_selector'] : array(),
			);

			return;
		}

		if ( ! $this->_current_section ) {

			_doing_it_wrong(
				'Block::add_control',
				'You must call start_section() before adding controls.',
				'1.0.0'
			);

			return;
		}

		$control_id = ! empty( $args['id'] ) ? $args['id'] : false;

		if ( ! $control_id ) {
			_doing_it_wrong(
				'Block::add_control',
				'You must provide a control `id` parameter.',
				'1.0.0'
			);

			return;
		}

		// Ensure correct format of CSS variable prop
		$css_var = ! empty( $args['css_var'] ) ? (array) $args['css_var'] : array();

		if ( ! empty( $css_var ) ) {

			$css_var = array_merge(
				array(
					'prefix' => '--csm',
					'name'   => $control_id,
				),
				$css_var
			);

			$args['css_var'] = $css_var;
		}

		if ( $this->_current_tabs && $this->_current_tab ) {
			$this->register_children(
				array( $this->_current_section, $this->_current_tabs, $this->_current_tab ),
				$control_id,
				$args
			);
		} else {
			$this->register_children(
				array( $this->_current_section ),
				$control_id,
				$args
			);
		}

		if ( ! empty( $args['default'] ) ) {

			if ( is_null( $this->defaults ) ) {
				$this->defaults = array();
			}

			$this->defaults[ $control_id ] = $args['default'];
		}

		$this->css_props[ $control_id ] = array(
			'type'         => ! empty( $args['type'] ) ? $args['type'] : 'text',
			'css_var'      => ! empty( $args['css_var'] ) ? $args['css_var'] : array(),
			'css_selector' => ! empty( $args['css_selector'] ) ? $args['css_selector'] : array(),
		);
	}

	/**
	 * Start tabs group
	 *
	 * @param array $args
	 * @return void
	 */
	public function start_tabs( $args = array() ) {

		if ( ! $this->should_register_full_controls() ) {
			return;
		}

		if ( ! empty( $args['id'] ) ) {
			$id = $args['id'];
		} else {
			_doing_it_wrong(
				'Block::start_tabs',
				'You must provide a tabs `id` parameter.',
				'1.0.0'
			);
			return;
		}

		$this->_current_tabs = $id;

		$args['type'] = 'tabs';

		$this->register_children(
			array( $this->_current_section ),
			$this->_current_tabs,
			$args
		);
	}

	/**
	 * Start a new tab
	 *
	 * @param array $args
	 * @return void
	 */
	public function start_tab( $args = array() ) {

		if ( ! $this->should_register_full_controls() ) {
			return;
		}

		if ( ! empty( $args['id'] ) ) {
			$id = $args['id'];
		} else {
			_doing_it_wrong(
				'Block::start_tab',
				'You must provide a tabs `id` parameter.',
				'1.0.0'
			);
			return;
		}

		$this->_current_tab = $id;

		$args['type'] = 'tab';

		$this->register_children(
			array( $this->_current_section, $this->_current_tabs ),
			$this->_current_tab,
			$args
		);
	}

	/**
	 * End tabs group
	 *
	 * @return void
	 */
	public function end_tabs() {

		if ( ! $this->should_register_full_controls() ) {
			return;
		}

		$this->_current_tabs = null;
		$this->_current_tab   = null;
	}

	/**
	 * End the current tab
	 *
	 * @return void
	 */
	public function end_tab() {

		if ( ! $this->should_register_full_controls() ) {
			return;
		}

		$this->_current_tab = null;
	}

	/**
	 * Register children controls recursively
	 *
	 * This method registers child controls under the specified parent trace.
	 *
	 * @param array  $parents_trace Array of parent keys representing the trace.
	 *                              Example: ['section1', 'tabs1', 'tab1'].
	 *                              Each key must be a non-empty string.
	 * @param string $control_id    The control ID. Must be a unique, non-empty string.
	 *                              Example: 'control1'.
	 * @param array  $args          The control arguments. Should include control-specific
	 *                              settings such as 'type', 'label', etc.
	 *                              Example: ['type' => 'text', 'label' => 'Enter text'].
	 * @return void
	 */
	public function register_children( $parents_trace, $control_id, $args ) {

		// Validate $parents_trace to ensure it contains valid keys
		if ( ! is_array( $parents_trace ) || empty( $parents_trace ) ) {
			_doing_it_wrong(
				'Block::register_children',
				'The $parents_trace parameter must be a non-empty array of valid keys.',
				'1.0.0'
			);
			return;
		}

		// Validate $control_id to ensure it's a non-empty string
		if ( ! is_string( $control_id ) || empty( $control_id ) ) {
			_doing_it_wrong(
				'Block::register_children',
				'The $control_id parameter must be a non-empty string.',
				'1.0.0'
			);
			return;
		}

		// Validate $args to ensure it's an array
		if ( ! is_array( $args ) || empty( $args ) ) {
			_doing_it_wrong(
				'Block::register_children',
				'The $args parameter must be a non-empty array.',
				'1.0.0'
			);
			return;
		}

		$controls = &$this->controls_stack;

		foreach ( $parents_trace as $parent ) {

			if ( ! isset( $controls['children'][ $parent ] ) ) {
				_doing_it_wrong(
					'Block::register_children',
					"Parent control `$parent` not found in the controls stack.",
					'1.0.0'
				);
				return;
			}

			$controls = &$controls['children'][ $parent ];

			if ( ! isset( $controls['children'] ) ) {
				$controls['children'] = array();
			}

			// Check if is last parent in the trace and register the control
			if ( end( $parents_trace ) === $parent ) {
				$controls['children'][ $control_id ] = $args;
			}
		}
	}
}