<?php
/**
 * Sanitize interpolated block HTML for frontend / shortcode output.
 *
 * @package JOCBM
 *
 * Copyright (C) 2026 JayOnCode
 * License: GPL-2.0-or-later
 */

namespace JOCBM\Services;

defined( 'ABSPATH' ) || exit;

/**
 * Class BlockMarkup
 */
final class BlockMarkup {

	/**
	 * wp_kses allow-list for rendered block markup fragments.
	 *
	 * @return array<string, array<string, bool>>
	 */
	public static function allowed_html(): array {
		$global_attrs = array(
			'class'            => true,
			'style'            => true,
			'id'               => true,
			'role'             => true,
			'aria-hidden'      => true,
			'aria-label'       => true,
			'aria-labelledby'  => true,
			'aria-current'     => true,
			'data-bm-edit'     => true,
			'data-bm-surface'  => true,
			'data-bm-name'     => true,
			'data-bm-index'    => true,
			'data-bm-content-id' => true,
			'data-bm-card-index' => true,
			'data-bm-btn-index'  => true,
			'data-bm-el'       => true,
			'data-bm-tab-panel' => true,
			'data-bm-slider'   => true,
			'data-bm-slide-index' => true,
		);

		$tags = array(
			'section'    => $global_attrs,
			'article'    => $global_attrs,
			'div'        => $global_attrs,
			'span'       => $global_attrs,
			'p'          => $global_attrs,
			'h1'         => $global_attrs,
			'h2'         => $global_attrs,
			'h3'         => $global_attrs,
			'h4'         => $global_attrs,
			'h5'         => $global_attrs,
			'h6'         => $global_attrs,
			'a'          => array_merge(
				$global_attrs,
				array(
					'href'   => true,
					'target' => true,
					'rel'    => true,
				)
			),
			'ul'         => $global_attrs,
			'ol'         => $global_attrs,
			'li'         => $global_attrs,
			'blockquote' => $global_attrs,
			'cite'       => $global_attrs,
			'footer'     => $global_attrs,
			'figure'     => $global_attrs,
			'figcaption' => $global_attrs,
			'picture'    => $global_attrs,
			'source'     => array(
				'srcset' => true,
				'sizes'  => true,
				'type'   => true,
				'media'  => true,
			),
			// Media tags need data-bm-el so element-order CSS can target them.
			// Without it, img/video keep flex order:0 and sort above badge/header.
			'img'        => array_merge(
				$global_attrs,
				array(
					'src'      => true,
					'alt'      => true,
					'width'    => true,
					'height'   => true,
					'loading'  => true,
					'decoding' => true,
				)
			),
			'video'      => array_merge(
				$global_attrs,
				array(
					'src'         => true,
					'poster'      => true,
					'controls'    => true,
					'playsinline' => true,
					'muted'       => true,
					'loop'        => true,
					'autoplay'    => true,
				)
			),
			'iframe'     => array_merge(
				$global_attrs,
				array(
					'src'             => true,
					'title'           => true,
					'loading'         => true,
					'allow'           => true,
					'allowfullscreen' => true,
					'referrerpolicy'  => true,
					'frameborder'     => true,
				)
			),
			'label'      => array_merge(
				$global_attrs,
				array(
					'for' => true,
				)
			),
			// Accordion — native <details>/<summary> expand/collapse.
			'details'    => array_merge(
				$global_attrs,
				array(
					'open' => true,
					'name' => true,
				)
			),
			'summary'    => $global_attrs,
			'input'      => array(
				'class'    => true,
				'style'    => true,
				'type'     => true,
				'name'     => true,
				'id'       => true,
				'checked'  => true,
			),
			'strong'     => array(),
			'em'         => array(),
			'b'          => array(),
			'i'          => array(),
			'u'          => array(),
			's'          => array(),
			'br'         => array(),
			'hr'         => array(),
			'style'      => array(
				'type' => true,
			),
			// Lucide / svg-inline button icons (and svg-sprite <use>).
			'svg'        => array_merge(
				$global_attrs,
				array(
					'xmlns'          => true,
					'width'          => true,
					'height'         => true,
					'viewbox'        => true,
					'fill'           => true,
					'stroke'         => true,
					'stroke-width'   => true,
					'stroke-linecap' => true,
					'stroke-linejoin'=> true,
					'focusable'      => true,
					'preserveaspectratio' => true,
				)
			),
			'g'          => array_merge(
				$global_attrs,
				array(
					'fill'            => true,
					'stroke'          => true,
					'stroke-width'    => true,
					'stroke-linecap'  => true,
					'stroke-linejoin' => true,
					'transform'       => true,
					'opacity'         => true,
				)
			),
			'path'       => array(
				'class'           => true,
				'style'           => true,
				'd'               => true,
				'fill'            => true,
				'stroke'          => true,
				'stroke-width'    => true,
				'stroke-linecap'  => true,
				'stroke-linejoin' => true,
				'transform'       => true,
				'opacity'         => true,
			),
			'circle'     => array(
				'class'           => true,
				'style'           => true,
				'cx'              => true,
				'cy'              => true,
				'r'               => true,
				'fill'            => true,
				'stroke'          => true,
				'stroke-width'    => true,
				'stroke-linecap'  => true,
				'stroke-linejoin' => true,
				'transform'       => true,
				'opacity'         => true,
			),
			'ellipse'    => array(
				'class'           => true,
				'style'           => true,
				'cx'              => true,
				'cy'              => true,
				'rx'              => true,
				'ry'              => true,
				'fill'            => true,
				'stroke'          => true,
				'stroke-width'    => true,
				'transform'       => true,
				'opacity'         => true,
			),
			'line'       => array(
				'class'           => true,
				'style'           => true,
				'x1'              => true,
				'y1'              => true,
				'x2'              => true,
				'y2'              => true,
				'fill'            => true,
				'stroke'          => true,
				'stroke-width'    => true,
				'stroke-linecap'  => true,
				'stroke-linejoin' => true,
				'transform'       => true,
				'opacity'         => true,
			),
			'polyline'   => array(
				'class'           => true,
				'style'           => true,
				'points'          => true,
				'fill'            => true,
				'stroke'          => true,
				'stroke-width'    => true,
				'stroke-linecap'  => true,
				'stroke-linejoin' => true,
				'transform'       => true,
				'opacity'         => true,
			),
			'polygon'    => array(
				'class'           => true,
				'style'           => true,
				'points'          => true,
				'fill'            => true,
				'stroke'          => true,
				'stroke-width'    => true,
				'stroke-linecap'  => true,
				'stroke-linejoin' => true,
				'transform'       => true,
				'opacity'         => true,
			),
			'rect'       => array(
				'class'           => true,
				'style'           => true,
				'x'               => true,
				'y'               => true,
				'width'           => true,
				'height'          => true,
				'rx'              => true,
				'ry'              => true,
				'fill'            => true,
				'stroke'          => true,
				'stroke-width'    => true,
				'transform'       => true,
				'opacity'         => true,
			),
			'use'        => array(
				'class'      => true,
				'style'      => true,
				'href'       => true,
				'xlink:href' => true,
				'x'          => true,
				'y'          => true,
				'width'      => true,
				'height'     => true,
			),
		);

		/**
		 * Filter block markup allow-list used before frontend output.
		 *
		 * @param array<string, array<string, bool>> $tags Allowed tags.
		 */
		return apply_filters( 'jocbm_block_markup_allowed_html', $tags );
	}

	/**
	 * Sanitize rendered block HTML before returning from render callbacks.
	 *
	 * @param string $html Interpolated markup.
	 */
	public static function sanitize_output( string $html ): string {
		$html = trim( $html );
		if ( '' === $html ) {
			return '';
		}

		return wp_kses( $html, self::allowed_html() );
	}

	/**
	 * Wrap sanitized inner HTML in a block container.
	 *
	 * @param string $wrapper_attributes Escaped attributes from get_block_wrapper_attributes().
	 * @param string $inner_html         Interpolated inner HTML.
	 */
	public static function wrap_block_output( string $wrapper_attributes, string $inner_html ): string {
		return '<div ' . $wrapper_attributes . '>' . self::sanitize_output( $inner_html ) . '</div>';
	}

	/**
	 * Build a shortcode/content wrapper with escaped class names.
	 *
	 * @param string $class        CSS classes.
	 * @param string $extra_attrs  Pre-escaped attribute string (leading space optional).
	 * @param string $inner_html   Interpolated inner HTML.
	 */
	public static function wrap_content_output( string $class, string $extra_attrs, string $inner_html ): string {
		return sprintf(
			'<div class="%1$s"%2$s>%3$s</div>',
			esc_attr( $class ),
			$extra_attrs,
			self::sanitize_output( $inner_html )
		);
	}
}
