<?php
/**
 * Product shortcode renderer bridge.
 *
 * @package Affilun\Presentation\Shortcode
 */

declare(strict_types=1);

namespace Affilun\Presentation\Shortcode;

/**
 * Parses Classic Editor shortcode attributes and delegates to block renderers.
 */
final class ProductShortcodeRenderer {

	/**
	 * Card renderer callable.
	 *
	 * @var callable(array<string,mixed>): string
	 */
	private $cardRenderer;

	/**
	 * @param callable $cardRenderer Card renderer.
	 *
	 * @phpstan-param callable(array<string,mixed>): string $cardRenderer
	 */
	public function __construct( callable $cardRenderer ) {
		$this->cardRenderer = $cardRenderer;
	}

	/**
	 * Render `[affilun_product_card]`.
	 *
	 * @param mixed $attributes Shortcode attributes.
	 */
	public function renderCardShortcode( mixed $attributes ): string {
		$renderer = $this->cardRenderer;
		return $renderer( $this->cardAttributes( is_array( $attributes ) ? $attributes : array() ) );
	}


	/**
	 * Convert card shortcode attributes to block attributes.
	 *
	 * @param array<mixed> $attributes Shortcode attributes.
	 *
	 * @return array<string,mixed>
	 */
	public function cardAttributes( array $attributes ): array {
		$parsed = array();
		$id     = self::positiveInt( $attributes['product_id'] ?? $attributes['id'] ?? null );
		if ( $id !== null ) {
			$parsed['productId'] = $id;
		}

		$template = self::enumString(
			$attributes['template'] ?? null,
			array( 'single-default-card', 'single-compact-card', 'single-minimal-cta', 'single-fallback-card' )
		);
		if ( $template !== null ) {
			$parsed['template'] = $template;
		}

		$this->copyBool( $attributes, $parsed, 'show_price', 'showPrice' );
		$this->copyBool( $attributes, $parsed, 'show_brand', 'showBrand' );
		$this->copyBool( $attributes, $parsed, 'show_description', 'showDescription' );
		$this->copyBool( $attributes, $parsed, 'show_provider_badge', 'showProviderBadge' );
		$this->copyBool( $attributes, $parsed, 'open_in_new_tab', 'openInNewTab' );
		$this->copyBool( $attributes, $parsed, 'nofollow', 'nofollow' );
		$this->copyBool( $attributes, $parsed, 'sponsored', 'sponsored' );
		$this->copyBool( $attributes, $parsed, 'tracking_enabled', 'trackingEnabled' );
		$this->copyString( $attributes, $parsed, 'microcopy', 'microcopyText', 80 );
		$this->copyEnum( $attributes, $parsed, 'button_layout', 'buttonLayout', array( 'wrap', 'stacked', 'full' ) );
		$this->copyEnum( $attributes, $parsed, 'button_shape', 'buttonShape', array( 'square', 'rounded', 'pill' ) );
		$this->copyEnum( $attributes, $parsed, 'button_tone', 'buttonTone', array( 'brand', 'muted', 'monochrome' ) );

		return $parsed;
	}


	/**
	 * Copy a boolean attribute when supplied.
	 *
	 * @param array<mixed>        $source Source attributes.
	 * @param array<string,mixed> $target Target attributes.
	 * @param string              $sourceKey Source key.
	 * @param string              $targetKey Target key.
	 */
	private function copyBool( array $source, array &$target, string $sourceKey, string $targetKey ): void {
		$value = self::boolValue( $source[ $sourceKey ] ?? $source[ $targetKey ] ?? null );
		if ( $value !== null ) {
			$target[ $targetKey ] = $value;
		}
	}

	/**
	 * Copy a short text attribute when supplied.
	 *
	 * @param array<mixed>        $source Source attributes.
	 * @param array<string,mixed> $target Target attributes.
	 * @param string              $sourceKey Source key.
	 * @param string              $targetKey Target key.
	 * @param int                 $maxLength Maximum length.
	 */
	private function copyString(
		array $source,
		array &$target,
		string $sourceKey,
		string $targetKey,
		int $maxLength
	): void {
		$value = self::shortString( $source[ $sourceKey ] ?? $source[ $targetKey ] ?? null, $maxLength );
		if ( $value !== null ) {
			$target[ $targetKey ] = $value;
		}
	}

	/**
	 * Copy an enum attribute when supplied.
	 *
	 * @param array<mixed>        $source Source attributes.
	 * @param array<string,mixed> $target Target attributes.
	 * @param string              $sourceKey Source key.
	 * @param string              $targetKey Target key.
	 * @param list<string>        $allowed Allowed values.
	 */
	private function copyEnum(
		array $source,
		array &$target,
		string $sourceKey,
		string $targetKey,
		array $allowed
	): void {
		$value = self::enumString( $source[ $sourceKey ] ?? $source[ $targetKey ] ?? null, $allowed );
		if ( $value !== null ) {
			$target[ $targetKey ] = $value;
		}
	}

	/**
	 * Parse a positive integer.
	 *
	 * @param mixed $value Value.
	 */
	private static function positiveInt( mixed $value ): ?int {
		if ( is_int( $value ) && $value > 0 ) {
			return $value;
		}
		if ( is_string( $value ) && preg_match( '/\A\d+\z/', trim( $value ) ) === 1 ) {
			$parsed = (int) trim( $value );
			return $parsed > 0 ? $parsed : null;
		}
		return null;
	}


	/**
	 * Parse a boolean.
	 *
	 * @param mixed $value Value.
	 */
	private static function boolValue( mixed $value ): ?bool {
		if ( is_bool( $value ) ) {
			return $value;
		}
		if ( ! is_scalar( $value ) ) {
			return null;
		}
		$normalized = strtolower( trim( (string) $value ) );
		if ( in_array( $normalized, array( '1', 'true', 'yes', 'on' ), true ) ) {
			return true;
		}
		if ( in_array( $normalized, array( '0', 'false', 'no', 'off' ), true ) ) {
			return false;
		}
		return null;
	}

	/**
	 * Parse an enum string.
	 *
	 * @param mixed        $value Value.
	 * @param list<string> $allowed Allowed values.
	 */
	private static function enumString( mixed $value, array $allowed ): ?string {
		if ( ! is_scalar( $value ) ) {
			return null;
		}
		$normalized = strtolower( trim( (string) $value ) );
		return in_array( $normalized, $allowed, true ) ? $normalized : null;
	}

	/**
	 * Parse a short string.
	 *
	 * @param mixed $value Value.
	 * @param int   $maxLength Maximum length.
	 */
	private static function shortString( mixed $value, int $maxLength ): ?string {
		if ( ! is_scalar( $value ) ) {
			return null;
		}
		$normalized = trim( preg_replace( '/\s+/', ' ', (string) $value ) ?? (string) $value );
		if ( $normalized === '' ) {
			return null;
		}
		if ( function_exists( 'mb_strlen' ) && function_exists( 'mb_substr' ) ) {
			return mb_strlen( $normalized ) > $maxLength ? mb_substr( $normalized, 0, $maxLength ) : $normalized;
		}
		return strlen( $normalized ) > $maxLength ? substr( $normalized, 0, $maxLength ) : $normalized;
	}
}
