<?php
/**
 * Shared embed width wrapper for RSM blocks, shortcodes, and widgets.
 *
 * @package Rsmspark_Suite
 */

defined( 'ABSPATH' ) || exit;

/**
 * Rsmspark_Embed_Chrome
 */
final class Rsmspark_Embed_Chrome {

	const MIN_WIDTH           = 40;
	const MAX_WIDTH           = 100;
	const DEFAULT_WIDTH       = 100;
	const DEFAULT_ALIGN       = 'center';
	const MOBILE_BREAKPOINT   = 640;
	const TEXT_DOMAIN = 'rsm-content-widgets';

	/**
	 * @var bool
	 */
	private static $booted = false;

	/**
	 * @return void
	 */
	public static function init() {
		if ( self::$booted ) {
			return;
		}

		self::$booted = true;

		add_filter( 'register_block_type_args', array( __CLASS__, 'register_block_type_args' ), 12, 2 );
		add_action( 'init', array( __CLASS__, 'ensure_registered_attributes' ), 26 );
		add_filter( 'render_block', array( __CLASS__, 'render_block' ), 35, 2 );
		add_action( 'wp_enqueue_scripts', array( __CLASS__, 'enqueue_front' ), 25 );
		add_action( 'enqueue_block_editor_assets', array( __CLASS__, 'enqueue_editor' ), 25 );
	}

	/**
	 * Block attribute schema merged into RSM blocks.
	 *
	 * @return array<string, array<string, mixed>>
	 */
	public static function attribute_schema() {
		return array(
			'rsmEmbedWidth' => array(
				'type'    => 'number',
				'default' => self::DEFAULT_WIDTH,
			),
			'rsmEmbedAlign' => array(
				'type'    => 'string',
				'default' => self::DEFAULT_ALIGN,
			),
		);
	}

	/**
	 * @return string Absolute path to the rsm-shared directory that loaded this class.
	 */
	public static function suite_root_dir() {
		return dirname( __DIR__ );
	}

	/**
	 * @return string
	 */
	public static function suite_bootstrap_file() {
		return self::suite_root_dir() . '/bootstrap.php';
	}

	/**
	 * @param string $relative Path relative to rsm-shared root.
	 * @return string
	 */
	public static function shared_asset_path( $relative ) {
		return self::suite_root_dir() . '/' . ltrim( (string) $relative, '/' );
	}

	/**
	 * @return string
	 */
	public static function shared_asset_url( $relative ) {
		$relative = ltrim( (string) $relative, '/' );
		$local    = self::shared_asset_path( $relative );

		if ( is_readable( $local ) ) {
			return plugins_url( $relative, self::suite_bootstrap_file() );
		}

		if ( defined( 'RSMSPARK_SUITE_DIR' ) && defined( 'RSMSPARK_SUITE_URL' ) ) {
			$fallback = trailingslashit( RSMSPARK_SUITE_DIR ) . $relative;
			if ( is_readable( $fallback ) ) {
				return trailingslashit( RSMSPARK_SUITE_URL ) . $relative;
			}
		}

		return plugins_url( $relative, self::suite_bootstrap_file() );
	}

	/**
	 * @return string
	 */
	public static function shared_asset_version() {
		return defined( 'RSMSPARK_SUITE_VERSION' ) ? (string) RSMSPARK_SUITE_VERSION : '1.0.0';
	}

	/**
	 * Block prefixes that receive embed width controls.
	 *
	 * @return string[]
	 */
	public static function block_prefixes() {
		$prefixes = array(
			'rsm-content-widgets/',
			'rsm-cw-compat/',
			'rsm/',
			'openrsm/',
		);

		/**
		 * Filter block name prefixes that receive embed width attributes.
		 *
		 * @param string[] $prefixes Prefixes including trailing slash.
		 */
		return apply_filters( 'rsmspark_embed_chrome_block_prefixes', $prefixes );
	}

	/**
	 * @param string $name Block name.
	 * @return bool
	 */
	public static function is_embed_block_name( $name ) {
		$name = (string) $name;
		if ( '' === $name ) {
			return false;
		}

		foreach ( self::block_prefixes() as $prefix ) {
			if ( str_starts_with( $name, (string) $prefix ) ) {
				return (bool) apply_filters( 'rsmspark_embed_chrome_is_embed_block', true, $name );
			}
		}

		if ( str_starts_with( $name, 'rsm-' ) && str_contains( $name, '/' ) ) {
			return (bool) apply_filters( 'rsmspark_embed_chrome_is_embed_block', true, $name );
		}

		return (bool) apply_filters( 'rsmspark_embed_chrome_is_embed_block', false, $name );
	}

	/**
	 * @param array<string, mixed> $args Block type args.
	 * @param string               $name Block name.
	 * @return array<string, mixed>
	 */
	public static function register_block_type_args( $args, $name ) {
		if ( ! is_array( $args ) ) {
			$args = array();
		}

		if ( ! self::is_embed_block_name( (string) $name ) ) {
			return $args;
		}

		if ( ! isset( $args['attributes'] ) || ! is_array( $args['attributes'] ) ) {
			$args['attributes'] = array();
		}

		$args['attributes'] = array_merge( $args['attributes'], self::attribute_schema() );

		return $args;
	}

	/**
	 * @return void
	 */
	public static function ensure_registered_attributes() {
		if ( ! class_exists( 'WP_Block_Type_Registry', false ) ) {
			return;
		}

		$registry = WP_Block_Type_Registry::get_instance();

		foreach ( $registry->get_all_registered() as $name => $block_type ) {
			if ( ! self::is_embed_block_name( (string) $name ) ) {
				continue;
			}

			$existing               = is_array( $block_type->attributes ) ? $block_type->attributes : array();
			$block_type->attributes = array_merge( $existing, self::attribute_schema() );
		}
	}

	/**
	 * @param int|string $width Raw width.
	 * @return int
	 */
	public static function sanitize_width( $width ) {
		$width = (int) $width;
		if ( $width <= 0 ) {
			return self::DEFAULT_WIDTH;
		}

		return max( self::MIN_WIDTH, min( self::MAX_WIDTH, $width ) );
	}

	/**
	 * @param string $align Raw alignment.
	 * @return string
	 */
	public static function sanitize_align( $align ) {
		$align = sanitize_key( (string) $align );
		if ( ! in_array( $align, array( 'left', 'center', 'right' ), true ) ) {
			return self::DEFAULT_ALIGN;
		}

		return $align;
	}

	/**
	 * @param array<string, mixed> $attrs Block attributes or shortcode atts.
	 * @return array{width:int, align:string}
	 */
	public static function settings_from_array( array $attrs ) {
		$width = self::DEFAULT_WIDTH;
		if ( array_key_exists( 'rsmEmbedWidth', $attrs ) ) {
			$width = self::sanitize_width( $attrs['rsmEmbedWidth'] );
		} elseif ( array_key_exists( 'embed_width', $attrs ) ) {
			$width = self::sanitize_width( $attrs['embed_width'] );
		} elseif ( array_key_exists( 'width', $attrs ) ) {
			$width = self::sanitize_width( $attrs['width'] );
		}

		$align = self::DEFAULT_ALIGN;
		if ( array_key_exists( 'rsmEmbedAlign', $attrs ) ) {
			$align = self::sanitize_align( $attrs['rsmEmbedAlign'] );
		} elseif ( array_key_exists( 'embed_align', $attrs ) ) {
			$align = self::sanitize_align( $attrs['embed_align'] );
		} elseif ( array_key_exists( 'align', $attrs ) ) {
			$align = self::sanitize_align( $attrs['align'] );
		}

		$settings = array(
			'width' => $width,
			'align' => $align,
		);

		/**
		 * Filter parsed embed width settings before render.
		 *
		 * @param array{width:int, align:string} $settings Parsed settings.
		 * @param array<string, mixed>          $attrs    Source attributes.
		 */
		return apply_filters( 'rsmspark_embed_chrome_settings', $settings, $attrs );
	}

	/**
	 * @param array<string, mixed> $atts Shortcode attributes.
	 * @return array{width:int, align:string}
	 */
	public static function settings_from_atts( array $atts ) {
		return self::settings_from_array( $atts );
	}

	/**
	 * @param array{width:int, align:string} $settings Parsed settings.
	 * @return bool
	 */
	public static function is_active( array $settings ) {
		return (int) ( $settings['width'] ?? self::DEFAULT_WIDTH ) < self::DEFAULT_WIDTH
			|| self::DEFAULT_ALIGN !== (string) ( $settings['align'] ?? self::DEFAULT_ALIGN );
	}

	/**
	 * @param array{width:int, align:string} $settings Parsed settings.
	 * @return string[]
	 */
	public static function wrapper_classes( array $settings ) {
		$classes = array( 'rsm-embed', 'rsm-embed--align-' . self::sanitize_align( (string) ( $settings['align'] ?? self::DEFAULT_ALIGN ) ) );

		return $classes;
	}

	/**
	 * @param array{width:int, align:string} $settings Parsed settings.
	 * @return string
	 */
	public static function wrapper_style( array $settings ) {
		$width = self::sanitize_width( $settings['width'] ?? self::DEFAULT_WIDTH );

		return '--rsm-embed-width:' . $width . '%';
	}

	/**
	 * Wrap rendered HTML when embed width or alignment is non-default.
	 *
	 * @param string                           $html     Inner markup.
	 * @param array{width:int, align:string}   $settings Parsed settings.
	 * @return string
	 */
	public static function wrap( $html, array $settings ) {
		$html = self::kses_inner_html( (string) $html );
		if ( '' === trim( $html ) ) {
			return $html;
		}

		if ( ! self::is_active( $settings ) ) {
			return $html;
		}

		$classes = array_map( 'sanitize_html_class', self::wrapper_classes( $settings ) );
		$style   = esc_attr( self::wrapper_style( $settings ) );

		return '<div class="' . esc_attr( implode( ' ', $classes ) ) . '" style="' . $style . '">'
			. '<div class="rsm-embed__inner">' . $html . '</div></div>';
	}

	/**
	 * Escape inner HTML before wrapping or returning from render_block.
	 *
	 * Prefer the host plugin allowlist when present (Content Widgets Kses);
	 * otherwise fall back to wp_kses_post. Hosts may filter the allowlist.
	 *
	 * @param string $html Inner markup.
	 * @return string
	 */
	public static function kses_inner_html( $html ) {
		$html = (string) $html;
		if ( '' === $html ) {
			return $html;
		}

		$allowed = null;
		if ( class_exists( 'Rsmspark_Content_Widgets_Kses', false )
			&& method_exists( 'Rsmspark_Content_Widgets_Kses', 'allowed_html' )
		) {
			$allowed = Rsmspark_Content_Widgets_Kses::allowed_html();
		}

		/**
		 * Filter allowed HTML for embed-chrome wrap / render_block returns.
		 *
		 * @param array<string, mixed>|null $allowed Allowed tags for wp_kses, or null for wp_kses_post.
		 * @param string                    $html    Inner HTML.
		 */
		$allowed = apply_filters( 'rsmspark_embed_chrome_allowed_html', $allowed, $html );

		if ( is_array( $allowed ) ) {
			return wp_kses( $html, $allowed );
		}

		return wp_kses_post( $html );
	}

	/**
	 * @param string $content Block HTML.
	 * @param array  $block   Block data.
	 * @return string
	 */
	public static function render_block( $content, $block ) {
		$content = (string) $content;
		if ( '' === trim( $content ) || ! is_array( $block ) ) {
			return $content;
		}

		$name = (string) ( $block['blockName'] ?? '' );
		if ( ! self::is_embed_block_name( $name ) ) {
			return $content;
		}

		$attrs    = is_array( $block['attrs'] ?? null ) ? $block['attrs'] : array();
		$settings = self::settings_from_array( $attrs );

		/**
		 * Skip embed wrap for specific blocks or contexts.
		 *
		 * @param bool                 $skip     Whether to skip wrapping.
		 * @param array{width:int, align:string} $settings Parsed settings.
		 * @param array                $block    Block data.
		 * @param string               $content  Block HTML.
		 */
		if ( apply_filters( 'rsmspark_embed_chrome_skip_wrap', false, $settings, $block, $content ) ) {
			// Still late-escape returned markup for Plugin Check / render_block filter rules.
			return self::kses_inner_html( $content );
		}

		return self::wrap( $content, $settings );
	}

	/**
	 * @return void
	 */
	public static function enqueue_front() {
		if ( is_admin() ) {
			return;
		}

		$css_path = self::shared_asset_path( 'assets/css/rsm-embed-chrome.css' );
		if ( ! is_readable( $css_path ) ) {
			return;
		}

		wp_enqueue_style(
			'rsm-embed-chrome',
			self::shared_asset_url( 'assets/css/rsm-embed-chrome.css' ),
			array(),
			(string) filemtime( $css_path )
		);
	}

	/**
	 * @return array<string, string>
	 */
	public static function editor_i18n() {
		return array(
			'panelEmbed'       => __( 'Embed width', 'rsm-content-widgets' ),
			'embedWidth'       => __( 'Width (%)', 'rsm-content-widgets' ),
			'embedWidthHelp'   => __(
				'Percentage of the available space in this column or content area—not the full browser width. Your theme or layout (content width, wide/full blocks, page-builder columns) already defines that space; this setting scales the block within it. 100% fills the column. On small screens, width is always 100%.',
				'rsm-content-widgets'
			),
			'embedAlign'       => __( 'Alignment', 'rsm-content-widgets' ),
			'embedAlignHelp' => __(
				'When width is below 100%, choose how the block sits in the column.',
				'rsm-content-widgets'
			),
			'embedAlignLeft'   => __( 'Left', 'rsm-content-widgets' ),
			'embedAlignCenter' => __( 'Center', 'rsm-content-widgets' ),
			'embedAlignRight'  => __( 'Right', 'rsm-content-widgets' ),
		);
	}

	/**
	 * @return void
	 */
	public static function enqueue_editor() {
		wp_enqueue_style(
			'rsm-embed-chrome',
			self::shared_asset_url( 'assets/css/rsm-embed-chrome.css' ),
			array(),
			self::shared_asset_version()
		);

		if ( wp_script_is( 'rsmspark-rsf-block-controls', 'enqueued' ) ) {
			return;
		}

		wp_enqueue_script(
			'rsmspark-embed-chrome-controls',
			self::shared_asset_url( 'assets/js/rsm-embed-chrome-controls.js' ),
			array(
				'wp-hooks',
				'wp-i18n',
				'wp-element',
				'wp-components',
				'wp-block-editor',
				'wp-compose',
			),
			self::shared_asset_version(),
			true
		);

		wp_localize_script(
			'rsmspark-embed-chrome-controls',
			'rsmsparkEmbedChrome',
			array(
				'minWidth'  => self::MIN_WIDTH,
				'maxWidth'  => self::MAX_WIDTH,
				'i18n'      => self::editor_i18n(),
			)
		);
	}
}
