<?php
/**
 * LoopBlock Component file.
 *
 * @package Etch
 */

declare(strict_types=1);

namespace Etch\Preprocessor\Blocks;

use Etch\Preprocessor\Data\EtchData;
use Etch\Preprocessor\Data\EtchDataGlobalLoop;
use Etch\Preprocessor\Utilities\EtchParser;
use Etch\Preprocessor\Utilities\EtchTypeAsserter;
use Etch\Preprocessor\Utilities\LoopHandlerManager;

/**
 * LoopBlock class for processing Loop blocks with EtchData.
 */
class LoopBlock extends BaseBlock {

	/**
	 * Item ID.
	 *
	 * @var string|null
	 */
	private $itemId;

	/**
	 * Loop ID.
	 *
	 * @var string|null
	 */
	private $loopId;

	/**
	 * Target Item ID.
	 *
	 * @var string|null
	 */
	private $targetItemId;

	/**
	 * Target Path.
	 *
	 * @var string|null
	 */
	private $targetPath;

	/**
	 * Loop params given on the block.
	 *
	 * @var array<string, mixed>|null
	 */
	private $loop_params;

	/**
	 * Constructor for the LoopBlock class.
	 *
	 * @param WpBlock                   $block WordPress block data.
	 * @param EtchData                  $data Etch data instance.
	 * @param array<string, mixed>|null $context Parent context to inherit.
	 * @param BaseBlock|null            $parent The parent block.
	 */
	public function __construct( WpBlock $block, EtchData $data, $context = null, $parent = null ) {
		parent::__construct( $block, $data, $context, $parent );

		$this->itemId = isset( $this->etch_data->loop->itemId ) ? $this->etch_data->loop->itemId : null;
		$this->loopId = isset( $this->etch_data->loop->loopId ) ? $this->etch_data->loop->loopId : null;
		$this->targetItemId = isset( $this->etch_data->loop->targetItemId ) ? $this->etch_data->loop->targetItemId : null;
		$this->targetPath = isset( $this->etch_data->loop->targetPath ) ? $this->etch_data->loop->targetPath : null;
		$this->loop_params = $this->get_resolved_loop_params( $context );
	}

	/**
	 * Process the loop block and return the transformed block data.
	 *
	 * @return array<int, array<string, mixed>> Array of transformed blocks (multiple blocks replacing the loop).
	 */
	public function process(): array {

		if ( $this->loopId ) {
			return $this->process_loop_id();
		}

		if ( $this->targetItemId ) {
			return $this->process_target_item_id();
		}

		return $this->get_inner_blocks_as_raw_blocks();
	}

	/**
	 * Process a loop with targetItemId (internal data source).
	 *
	 * @return array<int, array<string, mixed>> Array of transformed blocks.
	 */
	private function process_target_item_id(): array {
		$context = $this->get_context();
		$fullPath = $this->targetItemId . ( $this->targetPath ? '.' . $this->targetPath : '' );

		$context_data = EtchParser::process_expression( $fullPath, $context );
		$inner_blocks = $this->get_inner_blocks();

		$final_block_tree = array();

		if ( ! is_array( $context_data ) ) {
			return array();
		}

		foreach ( $context_data as $index => $item_data ) {
			$item_block_tree = $this->process_inner_blocks_with_context( $inner_blocks, $item_data );
			$final_block_tree = array_merge( $final_block_tree, $item_block_tree );
		}

		return $final_block_tree;
	}

	/**
	 * Process a loop with loopId (external data source).
	 *
	 * @return array<int, array<string, mixed>> Array of transformed blocks.
	 */
	private function process_loop_id(): array {
		if ( ! $this->loopId ) {
			return $this->get_inner_blocks_as_raw_blocks();
		}

		$loop_data = LoopHandlerManager::get_loop_preset_data( $this->loopId, $this->loop_params ?? array() );

		$inner_blocks = $this->get_inner_blocks();

		// If no loop data or no inner blocks, return empty array
		if ( empty( $loop_data ) || empty( $inner_blocks ) ) {
			return array();
		}

		$final_block_tree = array();

		// For each item in the loop data, generate a set of inner blocks
		foreach ( $loop_data as $loop_item ) {
			$item_block_tree = $this->process_inner_blocks_with_context( $inner_blocks, $loop_item );
			$final_block_tree = array_merge( $final_block_tree, $item_block_tree );
		}

		return $final_block_tree;
	}

	/**
	 * Process inner blocks with a specific loop item context.
	 *
	 * @param array<int, BaseBlock> $inner_blocks The inner blocks to process.
	 * @param mixed                 $loop_item The current loop item data (can be array, string, int, etc.).
	 * @return array<int, array<string, mixed>> Array of processed block data.
	 */
	private function process_inner_blocks_with_context( array $inner_blocks, $loop_item ): array {
		$processed_blocks = array();

		foreach ( $inner_blocks as $inner_block ) {
			// Create a new context that includes the current loop item
			$loop_context = array_merge( $this->get_context(), array( $this->itemId => $loop_item ) );

			// Create a new block instance with the loop context
			$block_data = $inner_block->get_raw_block();
			$block = self::create_from_block( $block_data, $loop_context, $this );

			// Process the block and handle both single blocks and multiple block expansions
			$processed_result = $block->process();
			$this->add_processed_result_to_raw_blocks( $processed_result, $processed_blocks );
		}

		return $processed_blocks;
	}


	/**
	 * Resolve loop parameters by processing expressions in the context.
	 *
	 * @param array<string, mixed>|null $context The context array to use for resolving expressions.
	 * @return array<string, mixed>|null The resolved loop parameters or null if not set.
	 */
	private function get_resolved_loop_params( $context ) {
		if ( ! isset( $this->etch_data->loop->loopParams ) ) {
			return null;
		}

		if ( null === $context || ! is_array( $context ) ) {
			return $this->etch_data->loop->loopParams;
		}

		return array_map(
			fn( $value ) => is_string( $value )
			? EtchParser::process_expression( $value, $context )
			: $value,
			$this->etch_data->loop->loopParams
		);
	}
}
