<?php
/**
 * REST: full library layout preview (HTML + module CSS) for Visual Builder embeds.
 *
 * Divi's `module-render` endpoint only runs `do_blocks()` and does not collect {@see \ET\Builder\FrontEnd\Module\Style}
 * output or page custom CSS, so embedded previews look unstyled. This route mirrors the Blog module REST pattern.
 *
 * @package DiviSupreme
 */

declare(strict_types=1);

use ET\Builder\Framework\Breakpoint\Breakpoint;
use ET\Builder\FrontEnd\BlockParser\BlockParserStore;
use ET\Builder\FrontEnd\Module\ScriptData;
use ET\Builder\FrontEnd\Module\Style;
use ET\Builder\FrontEnd\Page;

if ( ! defined( 'ABSPATH' ) ) {
	die( 'Direct access forbidden.' );
}

require_once __DIR__ . '/dsm-css-preview-scope.php';
require_once __DIR__ . '/dsm-preview-script-scope.php';

/**
 * Registers `POST /dsm/v1/library-layout-preview`.
 */
final class DSM_Library_Layout_Preview_REST {

	/**
	 * Hook REST routes.
	 */
	public function __construct() {
		add_action( 'rest_api_init', array( $this, 'register_routes' ) );
	}

	/**
	 * @return void
	 */
	public function register_routes(): void {
		register_rest_route(
			'dsm/v1',
			'/library-layout-preview',
			array(
				'methods'             => \WP_REST_Server::CREATABLE,
				'callback'            => array( $this, 'rest_callback' ),
				'permission_callback' => array( $this, 'permission_callback' ),
				'args'                => array(
					'layoutId' => array(
						'required'          => true,
						'sanitize_callback' => 'absint',
					),
				),
			)
		);
	}

	/**
	 * @return bool
	 */
	public function permission_callback(): bool {
		return current_user_can( 'edit_posts' );
	}

	/**
	 * Wrap preview CSS so module/order-class rules only apply under this layout's renderer root.
	 *
	 * @param string $css       Combined custom + module CSS.
	 * @param int    $layout_id Library layout post ID.
	 * @return string
	 */
	private function dsm_scope_library_preview_css( string $css, int $layout_id ): string {
		if ( $layout_id <= 0 || '' === trim( $css ) ) {
			return $css;
		}
		$scope = '.dsm-library-layout-renderer[data-dsm-library-preview="' . absint( $layout_id ) . '"]';

		return \DiviSupreme\Includes\DsmCssPreviewScope::apply( $css, $scope );
	}


	/**
	 * Render layout blocks and return HTML + CSS strings for VB preview.
	 *
	 * @param \WP_REST_Request $request Request.
	 * @return \WP_REST_Response|\WP_Error
	 */
	public function rest_callback( \WP_REST_Request $request ) {
		$layout_id = (int) $request->get_param( 'layoutId' );

		if ( $layout_id <= 0 ) {
			return new \WP_Error(
				'dsm_invalid_layout_id',
				esc_html__( 'Invalid layout ID.', 'dsm-supreme-modules-pro-for-divi' ),
				array( 'status' => 400 )
			);
		}

		$post = get_post( $layout_id );
		if ( ! $post instanceof \WP_Post ) {
			return new \WP_Error(
				'dsm_layout_not_found',
				esc_html__( 'Layout not found.', 'dsm-supreme-modules-pro-for-divi' ),
				array( 'status' => 404 )
			);
		}

		if ( ! defined( 'ET_BUILDER_LAYOUT_POST_TYPE' ) || ET_BUILDER_LAYOUT_POST_TYPE !== $post->post_type ) {
			return new \WP_Error(
				'dsm_invalid_post_type',
				esc_html__( 'The requested post is not a Divi Library layout.', 'dsm-supreme-modules-pro-for-divi' ),
				array( 'status' => 400 )
			);
		}

		$layout_status   = (string) $post->post_status;
		$can_preview     = ( 'publish' === $layout_status )
			|| ( 'private' === $layout_status && current_user_can( 'read_post', $layout_id ) );
		if ( ! $can_preview ) {
			return new \WP_Error(
				'dsm_layout_not_previewable',
				esc_html__( 'This layout is not available for preview.', 'dsm-supreme-modules-pro-for-divi' ),
				array( 'status' => 403 )
			);
		}

		if ( ! function_exists( 'et_builder_render_layout' ) || ! class_exists( Style::class ) ) {
			return new \WP_Error(
				'dsm_builder_unavailable',
				esc_html__( 'Divi Builder is not available.', 'dsm-supreme-modules-pro-for-divi' ),
				array( 'status' => 503 )
			);
		}

		$content = (string) $post->post_content;

		// Standalone library preview: do not mark BlockParser "inner content" true here.
		// Inner-content mode reuses {@see BlockParserStore::maybe_new_instance()} and can leave
		// {@see ScrollEffectsUtils::is_scroll_effects_enabled()} without a full layout state, so
		// {@see ScriptData::get_data( 'scroll' )} stays empty (no `diviElementScrollData` parity with FE).
		// Theme Builder inner flag below still scopes module CSS for REST (Blog module pattern).
		$previous_inner = false;
		if ( class_exists( BlockParserStore::class ) ) {
			$previous_inner = BlockParserStore::is_rendering_inner_content();
			BlockParserStore::set_rendering_inner_content( false );
		}

		$previous_tb_inner = false;
		if ( class_exists( Style::class ) ) {
			$previous_tb_inner = Style::get_is_theme_builder_context_for_inner_content();
			Style::set_is_theme_builder_context_for_inner_content( true );
		}

		$stack_class = class_exists( 'ET_Post_Stack' );
		if ( $stack_class ) {
			ET_Post_Stack::replace( $post );
		}

		$html       = '';
		$custom_css = '';
		try {
			$rendered = et_builder_render_layout( $content );
			$html     = function_exists( 'et_core_intentionally_unescaped' )
				? (string) et_core_intentionally_unescaped( $rendered, 'html' )
				: (string) $rendered;

			if ( class_exists( Page::class ) ) {
				$custom_css = (string) Page::custom_css( $layout_id );
			}
		} finally {
			if ( $stack_class ) {
				ET_Post_Stack::restore();
			}
			if ( class_exists( Style::class ) ) {
				Style::set_is_theme_builder_context_for_inner_content( $previous_tb_inner );
			}
			if ( class_exists( BlockParserStore::class ) ) {
				BlockParserStore::set_rendering_inner_content( $previous_inner );
			}
		}

		$preview_scroll_data     = array();
		$preview_animation_data  = array();
		$preview_breakpoint_data = array();
		if ( class_exists( ScriptData::class ) ) {
			if ( class_exists( Breakpoint::class ) ) {
				Breakpoint::set_script_data();
			}
			$preview_root_prefix = \DiviSupreme\Includes\DsmPreviewScriptScope::library_layout_renderer_root_selector( $layout_id );
			$raw_scroll = ScriptData::get_data( 'scroll' );
			if ( ! empty( $raw_scroll ) ) {
				$preview_scroll_data = \DiviSupreme\Includes\DsmPreviewScriptScope::scope_scroll_data(
					$raw_scroll,
					$preview_root_prefix,
					$layout_id
				);
			}
			$raw_animation = ScriptData::get_data( 'animation' );
			if ( ! empty( $raw_animation ) ) {
				$preview_animation_data = \DiviSupreme\Includes\DsmPreviewScriptScope::scope_animation_data(
					$raw_animation,
					$preview_root_prefix
				);
			}
			$raw_breakpoint = ScriptData::get_data( 'breakpoint' );
			if ( ! empty( $raw_breakpoint ) ) {
				$preview_breakpoint_data = $raw_breakpoint;
			}
			ScriptData::reset();
		}

		$module_css = Style::render( 'default', 'presetNested', 'post' )
			. Style::render( 'default', 'preset', 'post' )
			. Style::render( 'default', 'presetGroup', 'post' )
			. Style::render( 'default', 'module', 'post' );

		Style::clear_styles_for_key( 'post' );
		Style::set_detected_module_types_for_inner_content( array() );

		$preview_css = $this->dsm_scope_library_preview_css( $custom_css . $module_css, $layout_id );

		return new \WP_REST_Response(
			array(
				'renderedHTML'          => $html,
				'previewCss'            => $preview_css,
				'previewScrollData'     => $preview_scroll_data,
				'previewAnimationData'  => $preview_animation_data,
				'previewBreakpointData' => $preview_breakpoint_data,
			)
		);
	}
}

new DSM_Library_Layout_Preview_REST();
