<?php
namespace Jet_Engine_Divi_Views\Components\Jet_Engine;

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

/**
 * Injects 'jet_loop_object' attribute into specific JetEngine Divi blocks
 * whenever they are saved through the Divi 5 Visual Builder.
 *
 * Works by intercepting the REST request before Divi's sync endpoints process it,
 * bypassing the strict saveverification phase which normally blocks manual
 * post_content modifications during save.
 */
class Attributes_Injector {

	const ATTR_NAME = 'jet_loop_object';
	const ATTR_VALUE = '$variable({"type":"content","value":{"name":"jet_loop_object"}})$';

	public function __construct() {
		// Hook late into rest_pre_dispatch to ensure we intercept just before route execution.
		add_filter( 'rest_pre_dispatch', [ $this, 'intercept_divi_save_request' ], 99, 3 );
	}

	/**
	 * Intercepts Divi 5 REST API save endpoints to modify block attributes.
	 *
	 * @param mixed           $result  Response to replace the requested version with. Can be anything a normal endpoint can return, or null to not hijack the request.
	 * @param WP_REST_Server  $server  Server instance.
	 * @param WP_REST_Request $request Request used to generate the response.
	 * @return mixed
	 */
	public function intercept_divi_save_request( $result, $server, $request ) {
		// Only run on POST requests.
		if ( 'POST' !== $request->get_method() ) {
			return $result;
		}

		$route = $request->get_route();
		$is_sync_route = false;
		$param_name = '';

		// Determine if this is a Divi 5 saving endpoint and identify the content param name.
		if ( '/divi/v1/sync-to-server' === $route || '/divi/v1/divi-library/save' === $route ) {
			$is_sync_route = true;
			$param_name = 'content';
		} elseif ( '/divi/v1/divi-library/create-item' === $route ) {
			$is_sync_route = true;
			$param_name = 'layout_content';
		}

		if ( ! $is_sync_route ) {
			return $result;
		}

		$modified = false;

		// 1. Handle standard post_content blocks
		if ( 'content' === $param_name ) {
			$content = $request->get_param( 'content' );

			if ( is_array( $content ) && ! empty( $content['post_content'] ) ) {
				$post_content = $content['post_content'];
				$new_post_content = $this->modify_content_blocks( $post_content, $modified );

				if ( $modified ) {
					$content['post_content'] = $new_post_content;
					$request->set_param( 'content', $content );
				}
			}
		} elseif ( 'layout_content' === $param_name ) {
			$post_content = $request->get_param( 'layout_content' );

			if ( ! empty( $post_content ) && is_string( $post_content ) ) {
				$new_post_content = $this->modify_content_blocks( $post_content, $modified );

				if ( $modified ) {
					$request->set_param( 'layout_content', $new_post_content );
				}
			}
		}

		// 2. Handle Divi 5 off_canvas_data (where actual block attributes are frequently stored)
		$off_canvas_data_raw = $request->get_param( 'off_canvas_data' );
		if ( ! empty( $off_canvas_data_raw ) ) {
			$canvas_modified = false;
			$new_canvas_data_raw = $this->modify_off_canvas_data( $off_canvas_data_raw, $canvas_modified );

			if ( $canvas_modified ) {
				$request->set_param( 'off_canvas_data', $new_canvas_data_raw );
			}
		}

		return $result;
	}

	/**
	 * Parses post content into blocks, modifies them, and re-serializes.
	 *
	 * @param string $post_content The raw post content string containing WP blocks.
	 * @param bool   $modified     Reference boolean to track if any changes were made.
	 * @return string The serialized modified post content.
	 */
	private function modify_content_blocks( $post_content, &$modified ) {
		// Ensure WP blocks functions are available.
		if ( ! function_exists( 'parse_blocks' ) || ! function_exists( 'serialize_blocks' ) ) {
			return $post_content;
		}

		$blocks = parse_blocks( $post_content );
		$blocks = $this->process_blocks_recursively( $blocks, $modified );

		if ( $modified ) {
			return serialize_blocks( $blocks );
		}

		return $post_content;
	}

	/**
	 * Iterates over blocks recursively to inject the target attribute.
	 *
	 * @param array $blocks   Array of parsed WordPress blocks.
	 * @param bool  $modified Reference boolean tracking if changes happened.
	 * @return array Modified array of blocks.
	 */
	private function process_blocks_recursively( $blocks, &$modified ) {
		$target_blocks = [
			'jet-engine-divi/dynamic-field',
			'jet-engine-divi/dynamic-link',
			'jet-engine-divi/dynamic-image',
			'jet-engine-divi/dynamic-repeater',
			'jet-engine-divi/dynamic-meta',
			'jet-engine-divi/dynamic-terms',
		];

		foreach ( $blocks as &$block ) {
			// Ensure blockName exists and matches our target blocks.
			if ( ! empty( $block['blockName'] ) && in_array( $block['blockName'], $target_blocks, true ) ) {
				if ( ! isset( $block['attrs'][ self::ATTR_NAME ] ) ) {
					$block['attrs'][ self::ATTR_NAME ] = self::ATTR_VALUE;
					$modified = true;
				}
			}

			// Recursively process any inner blocks.
			if ( ! empty( $block['innerBlocks'] ) && is_array( $block['innerBlocks'] ) ) {
				$block['innerBlocks'] = $this->process_blocks_recursively( $block['innerBlocks'], $modified );
			}
		}

		return $blocks;
	}

	/**
	 * Decodes, modifies, and re-encodes Divi 5's off_canvas_data JSON structure.
	 *
	 * @param string|array $off_canvas_data_raw The raw off_canvas_data parameter.
	 * @param bool         $modified            Reference boolean tracking if changes happened.
	 * @return string|array The modified off_canvas_data parameter.
	 */
	private function modify_off_canvas_data( $off_canvas_data_raw, &$modified ) {
		$is_string = is_string( $off_canvas_data_raw );

		if ( $is_string ) {
			$data = json_decode( $off_canvas_data_raw, true );
			if ( json_last_error() !== JSON_ERROR_NONE ) {
				// Try stripslashes fallback similar to Divi's internal sync
				$data = json_decode( stripslashes( $off_canvas_data_raw ), true );
				if ( json_last_error() !== JSON_ERROR_NONE ) {
					return $off_canvas_data_raw; // Give up
				}
			}
		} else {
			$data = $off_canvas_data_raw;
		}

		if ( ! is_array( $data ) || empty( $data['canvases'] ) ) {
			return $off_canvas_data_raw;
		}

		$target_blocks = [
			'jet-engine-divi/dynamic-field',
			'jet-engine-divi/dynamic-link',
		];

		foreach ( $data['canvases'] as &$canvas ) {
			if ( empty( $canvas['content'] ) || ! is_array( $canvas['content'] ) ) {
				continue;
			}

			foreach ( $canvas['content'] as &$node ) {
				if ( ! empty( $node['name'] ) && in_array( $node['name'], $target_blocks, true ) ) {
					if ( ! isset( $node['props']['attrs'][ self::ATTR_NAME ] ) ) {
						// Ensure arrays exist
						if ( ! isset( $node['props'] ) ) {
							$node['props'] = [];
						}
						if ( ! isset( $node['props']['attrs'] ) ) {
							$node['props']['attrs'] = [];
						}

						$node['props']['attrs'][ self::ATTR_NAME ] = self::ATTR_VALUE;
						$modified = true;
					}
				}
			}
		}

		if ( $modified && $is_string ) {
			// Re-encode and return as string. (Divi usually sends JSON with UNESCAPED_SLASHES)
			return wp_json_encode( $data, JSON_UNESCAPED_SLASHES );
		}

		return $modified ? $data : $off_canvas_data_raw;
	}

}