<?php
/**
 * Editor preview renderer backed by the registered block renderers.
 *
 * @package Affilun\Presentation\Rest
 */

declare(strict_types=1);

namespace Affilun\Presentation\Rest;

use Affilun\Application\Editor\UseCase\PreviewResult;
use Affilun\Presentation\Block\ProductCardBlockRenderer;
use InvalidArgumentException;

/**
 * Converts the editor-preview REST payload into normal block attributes and
 * delegates HTML generation to the same renderers used by WordPress dynamic
 * block rendering.
 */
final class BlockPreviewRenderer {

	/**
	 * Legacy REST preview attr aliases accepted by the older preview use case.
	 *
	 * @var array<string,string>
	 */
	private const ATTR_ALIASES = array(
		'show_price'              => 'showPrice',
		'show_brand'              => 'showBrand',
		'show_description'        => 'showDescription',
		'show_provider_badge'     => 'showProviderBadge',
		'show_disclaimer'         => 'showDisclaimer',
		'open_in_new_tab'         => 'openInNewTab',
		'tracking_enabled'        => 'trackingEnabled',
		'button_labels'           => 'buttonLabels',
		'custom_buttons'          => 'customButtons',
		'template_variant'        => 'templateVariant',
		'button_layout'           => 'buttonLayout',
		'button_shape'            => 'buttonShape',
		'button_tone'             => 'buttonTone',
		'microcopy_text'          => 'microcopyText',
		'microcopy_start_at'      => 'microcopyStartAt',
		'microcopy_end_at'        => 'microcopyEndAt',
		'ab_testing_enabled'      => 'abTestingEnabled',
		'ab_button_label_variant' => 'abButtonLabelVariant',
		'ab_microcopy_variant'    => 'abMicrocopyVariant',
		'custom_css_class'        => 'customCssClass',
		'item_template'           => 'itemTemplate',
	);

	/**
	 * Single-product block renderer used by the front-end callback.
	 */
	private ProductCardBlockRenderer $cardRenderer;

	/**
	 * Construct the adapter with the canonical card block renderer.
	 *
	 * @param ProductCardBlockRenderer $cardRenderer Single-product block renderer.
	 */
	public function __construct( ProductCardBlockRenderer $cardRenderer ) {
		$this->cardRenderer = $cardRenderer;
	}

	/**
	 * Render the preview through the canonical block-renderer path.
	 *
	 * @param string               $kind       Preview kind: `single` or `grid`.
	 * @param list<string>         $productIds Product ids from the REST payload.
	 * @param string               $template   Requested template key.
	 * @param array<string, mixed> $attrs      Block attributes from the REST payload.
	 * @param int                  $userId     Current user id for cache-key parity.
	 */
	public function execute(
		string $kind,
		array $productIds,
		string $template,
		array $attrs,
		int $userId
	): PreviewResult {
		$this->assertKind( $kind );
		$this->assertTemplate( $template );
		$this->assertUserId( $userId );

		return $this->renderSingle( $productIds, $template, $attrs, $userId );
	}

	/**
	 * Render a single-product preview.
	 *
	 * @param list<string>         $productIds Product ids.
	 * @param string               $template   Requested template key.
	 * @param array<string, mixed> $attrs      Block attributes.
	 * @param int                  $userId     Current user id for cache-key parity.
	 */
	private function renderSingle( array $productIds, string $template, array $attrs, int $userId ): PreviewResult {
		if ( count( $productIds ) !== 1 ) {
			$message = sprintf(
				'BlockPreviewRenderer: kind "single" requires exactly one productId; got %d.',
				count( $productIds )
			);
			// phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped -- Developer-facing message, never rendered to HTML.
			throw new InvalidArgumentException( $message );
		}

		$productId                    = $this->parseProductId( $productIds[0] );
		$blockAttributes              = self::normalizeBlockAttrs( $attrs );
		$blockAttributes['productId'] = $productId;
		$blockAttributes['template']  = $template;

		$html = $this->cardRenderer->render( $blockAttributes );

		return new PreviewResult(
			array(
				'type'             => 'product-card',
				'template'         => $template,
				'product_id'       => $productId,
				'block_attributes' => $blockAttributes,
			),
			$html,
			self::composeCacheKey( $userId, 'single', $productIds, $template ),
			false
		);
	}


	/**
	 * @param array<string, mixed> $attrs Raw attrs.
	 *
	 * @return array<string, mixed>
	 */
	private static function normalizeBlockAttrs( array $attrs ): array {
		$normalized = $attrs;
		foreach ( self::ATTR_ALIASES as $legacyKey => $blockKey ) {
			if ( array_key_exists( $legacyKey, $attrs ) && ! array_key_exists( $blockKey, $normalized ) ) {
				$normalized[ $blockKey ] = $attrs[ $legacyKey ];
			}
		}

		return $normalized;
	}

	/**
	 * Validate the preview kind token.
	 *
	 * @param string $kind Preview kind.
	 */
	private function assertKind( string $kind ): void {
		if ( ! in_array( $kind, array( 'single' ), true ) ) {
			$message = sprintf( 'BlockPreviewRenderer: kind must be "single"; got "%s".', $kind );
			// phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped -- Developer-facing message, never rendered to HTML.
			throw new InvalidArgumentException( $message );
		}
	}

	/**
	 * Validate the requested template key.
	 *
	 * @param string $template Template key.
	 */
	private function assertTemplate( string $template ): void {
		if ( trim( $template ) === '' ) {
			$message = 'BlockPreviewRenderer: template must be a non-empty string.';
			// phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped -- Developer-facing message, never rendered to HTML.
			throw new InvalidArgumentException( $message );
		}
	}

	/**
	 * Validate the current user id used for preview cache-key parity.
	 *
	 * @param int $userId Current user id.
	 */
	private function assertUserId( int $userId ): void {
		if ( $userId < 1 ) {
			$message = sprintf( 'BlockPreviewRenderer: userId must be >= 1; got %d.', $userId );
			// phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped -- Developer-facing message, never rendered to HTML.
			throw new InvalidArgumentException( $message );
		}
	}

	/**
	 * Parse a REST product-id value into a positive integer.
	 *
	 * @param string $raw Raw product id.
	 */
	private function parseProductId( string $raw ): int {
		$parsed = filter_var(
			trim( $raw ),
			FILTER_VALIDATE_INT,
			array( 'options' => array( 'min_range' => 1 ) )
		);
		if ( $parsed === false ) {
			$message = 'BlockPreviewRenderer: productId must be a positive integer string.';
			// phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped -- Developer-facing message, never rendered to HTML.
			throw new InvalidArgumentException( $message );
		}

		return $parsed;
	}

	/**
	 * Compose the preview cache-key string used by the legacy use case.
	 *
	 * @param int          $userId     Current user id.
	 * @param string       $kind       Preview kind.
	 * @param list<string> $productIds Validated product-id list.
	 * @param string       $template   Template key.
	 */
	private static function composeCacheKey(
		int $userId,
		string $kind,
		array $productIds,
		string $template
	): string {
		return 'affilun:render:preview:'
			. $userId . ':'
			. $kind . ':'
			. implode( ',', $productIds ) . ':'
			. $template . ':v1';
	}
}
