<?php
// phpcs:disable WordPress.NamingConventions.PrefixAllGlobals -- Template/class file; variables are widget-scoped, not true globals.

/**
 * Storzen / shop-details.html markup for single-product gallery & related slider.
 *
 * @package storzen
 */

if (! defined('ABSPATH')) {
	exit;
}

/**
 * Image attachment IDs for main + gallery (no duplicates).
 *
 * @param \WC_Product $product Product.
 * @return int[]
 */
function storzen_product_gallery_image_ids($product)
{
	if (! $product instanceof \WC_Product) {
		return array();
	}
	$ids = array();
	$fid = $product->get_image_id();
	if ($fid) {
		$ids[] = (int) $fid;
	}
	foreach ($product->get_gallery_image_ids() as $gid) {
		$gid = (int) $gid;
		if ($gid && ! in_array($gid, $ids, true)) {
			$ids[] = $gid;
		}
	}
	return $ids;
}

/**
 * Print gallery: shop-details.html — preview-images-wrap, badges, wishlist, preview-images, thumb-images.
 *
 * @param \WC_Product $product Product.
 */
function storzen_print_single_product_gallery($product)
{
	if (! $product instanceof \WC_Product) {
		return;
	}
	$ids = storzen_product_gallery_image_ids($product);
	$pid = (int) $product->get_id();

	if (empty($ids)) {
		echo '<div class="preview-images-wrap mb-20 szp-preview-images-wrap"><p class="szp-no-gallery">' . esc_html__('No images available.', 'storzen') . '</p></div>';
		return;
	}

	$wishlist_url = apply_filters('storzen_single_product_wishlist_url', '#', $product);
?>
	<div class="szp-pd-gallery-root" data-gallery-product-id="<?php echo esc_attr((string) $pid); ?>">
		<div class="preview-images-wrap mb-20 wow fadeInUp delay-0-2s szp-preview-images-wrap">
			<div class="badges">
				<?php
				if ($product->is_on_sale()) {
					storzen_display_percentage_on_sale_badge();
				}
				if ($product->is_featured()) {
					echo '<span class="bgc-black">' . esc_html__('hot', 'storzen') . '</span>';
				}
				?>
			</div>
			<span class="add-wishlist szp-add-wishlist">
				<?php
				if (shortcode_exists('wtwp_icon')) {
					// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Shortcode output is already escaped by the wishlist plugin
					echo do_shortcode('[wtwp_icon product_id="' . absint($pid) . '"]');
				} else {
				?>
					<a href="<?php echo esc_url($wishlist_url); ?>">
						<i class="far fa-heart"></i>
					</a>
				<?php
				}
				?>
			</span>
			<div class="swiper szp-preview-swiper preview-images">
				<div class="swiper-wrapper">
					<?php
					foreach ($ids as $att_id) {
						$full = wp_get_attachment_image_src($att_id, 'full');
					?>
						<div class="swiper-slide preview-image-item">
							<div class="image">
								<?php if (! empty($full[0])) : ?>
									<a class="wco-image-popup" href="<?php echo esc_url($full[0]); ?>">
									<?php endif; ?>
									<?php
									echo wp_get_attachment_image(
										$att_id,
										'woocommerce_single',
										false,
										array(
											'alt' => $product->get_name(),
										)
									);
									?>
									<?php if (! empty($full[0])) : ?>
									</a>
								<?php endif; ?>
							</div>
						</div>
					<?php
					}
					?>
				</div>
			</div>
		</div>
		<div class="thumb-images mb-50 wow fadeInUp delay-0-2s szp-thumb-images">
			<div class="swiper szp-thumb-swiper">
				<div class="swiper-wrapper">
					<?php
					foreach ($ids as $att_id) {
					?>
						<div class="swiper-slide thumb-image-item">
							<div class="image">
								<?php
								echo wp_get_attachment_image(
									$att_id,
									'woocommerce_gallery_thumbnail',
									false,
									array(
										'alt' => $product->get_name(),
									)
								);
								?>
							</div>
						</div>
					<?php
					}
					?>
				</div>
			</div>
		</div>
	</div>
<?php
}

/**
 * Print gallery layout two: shop-details-2.html — tab preview + vertical thumbnails (no Swiper).
 *
 * @param \WC_Product $product Product.
 */
function storzen_print_single_product_gallery_layout_two($product)
{
	if (! $product instanceof \WC_Product) {
		return;
	}
	$ids = storzen_product_gallery_image_ids($product);
	$pid = (int) $product->get_id();

	if (empty($ids)) {
		echo '<div class="wco-row szp-pd-gallery-layout-two"><div class="wco-col-12"><div class="preview-images-wrap style-two mb-30 szp-preview-images-wrap"><p class="szp-no-gallery">' . esc_html__('No images available.', 'storzen') . '</p></div></div></div>';
		return;
	}
?>
	<div class="wco-row szp-pd-gallery-layout-two" data-gallery-product-id="<?php echo esc_attr((string) $pid); ?>">
		<div class="wco-col-xl-7 wco-col-md-8">
			<div class="preview-images-wrap style-two mb-30 wow fadeInUp delay-0-2s szp-preview-images-wrap-two">
				<div class="badges">
					<?php
					if ($product->is_on_sale()) {
						storzen_display_percentage_on_sale_badge();
					}
					if ($product->is_featured()) {
						echo '<span class="bgc-black">' . esc_html__('hot', 'storzen') . '</span>';
					}
					?>
				</div>
				<div class="tab-content preview-images-two">
					<?php
					foreach ($ids as $index => $att_id) {
						$full   = wp_get_attachment_image_src($att_id, 'full');
						$active = 0 === $index ? 'active show' : '';
					?>
						<div class="tab-pane fade preview-item <?php echo esc_attr($active); ?>" id="preview<?php echo esc_attr((string) ($index + 1)); ?>">
							<div class="preview-image-item">
								<div class="image">
									<?php if (! empty($full[0])) : ?>
										<a class="wco-image-popup" href="<?php echo esc_url($full[0]); ?>">
										<?php endif; ?>
										<?php
										echo wp_get_attachment_image(
											$att_id,
											'woocommerce_single',
											false,
											array(
												'alt' => $product->get_name(),
											)
										);
										?>
										<?php if (! empty($full[0])) : ?>
										</a>
									<?php endif; ?>
								</div>
							</div>
						</div>
					<?php
					}
					?>
				</div>
			</div>
		</div>
		<div class="wco-col-xl-5 wco-col-md-4">
			<div class="nav d-flex flex-md-column thumb-images-two mb-30 szp-thumb-images-two">
				<?php
				foreach ($ids as $index => $att_id) {
					$active = 0 === $index ? 'active' : '';
				?>
					<a href="#" data-img="preview<?php echo esc_attr((string) ($index + 1)); ?>" class="thumb-item <?php echo esc_attr($active); ?>">
						<?php
						echo wp_get_attachment_image(
							$att_id,
							'woocommerce_gallery_thumbnail',
							false,
							array(
								'alt' => $product->get_name(),
							)
						);
						?>
					</a>
				<?php
				}
				?>
			</div>
		</div>
	</div>
<?php
}

/**
 * Print gallery layout three: shop-details-3.html — thumbs + main preview in .thumbnail-and-preview (no Swiper).
 *
 * @param \WC_Product $product Product.
 */
function storzen_print_single_product_gallery_layout_three( $product ) {
	if ( ! $product instanceof \WC_Product ) {
		return;
	}
	$ids = storzen_product_gallery_image_ids( $product );
	$pid = (int) $product->get_id();

	if ( empty( $ids ) ) {
		echo '<div class="wco-row szp-pd-gallery-layout-three"><div class="wco-col-md-12"><div class="thumbnail-and-preview mb-40 szp-thumbnail-preview-three"><p class="szp-no-gallery">' . esc_html__( 'No images available.', 'storzen' ) . '</p></div></div></div>';
		return;
	}
	?>
	<div class="wco-row szp-pd-gallery-layout-three" data-gallery-product-id="<?php echo esc_attr( (string) $pid ); ?>">
		<div class="wco-col-md-12">
			<div class="thumbnail-and-preview mb-40 wow fadeInUp delay-0-2s szp-thumbnail-preview-three">
				<div class="nav flex-sm-column thumb-images-three">
					<?php
					foreach ( $ids as $index => $att_id ) {
						$active = 0 === $index ? 'active' : '';
						?>
						<a href="#" data-img="preview<?php echo esc_attr( (string) ( $index + 1 ) ); ?>" class="thumb-item <?php echo esc_attr( $active ); ?>">
							<?php
							echo wp_get_attachment_image(
								$att_id,
								'woocommerce_gallery_thumbnail',
								false,
								array(
									'alt' => $product->get_name(),
								)
							);
							?>
						</a>
						<?php
					}
					?>
				</div>
				<div class="preview-images-wrap style-three">
					<div class="badges">
						<?php
						if ( $product->is_on_sale() ) {
							storzen_display_percentage_on_sale_badge();
						}
						if ( $product->is_featured() ) {
							echo '<span class="bgc-black">' . esc_html__( 'hot', 'storzen' ) . '</span>';
						}
						?>
					</div>
					<div class="tab-content preview-images-two">
						<?php
						foreach ( $ids as $index => $att_id ) {
							$full   = wp_get_attachment_image_src( $att_id, 'full' );
							$active = 0 === $index ? 'active show' : '';
							?>
							<div class="tab-pane fade preview-item <?php echo esc_attr( $active ); ?>" id="preview<?php echo esc_attr( (string) ( $index + 1 ) ); ?>">
								<div class="preview-image-item">
									<div class="image">
										<?php if ( ! empty( $full[0] ) ) : ?>
											<a class="wco-image-popup" href="<?php echo esc_url( $full[0] ); ?>">
										<?php endif; ?>
										<?php
										echo wp_get_attachment_image(
											$att_id,
											'woocommerce_single',
											false,
											array(
												'alt' => $product->get_name(),
											)
										);
										?>
										<?php if ( ! empty( $full[0] ) ) : ?>
											</a>
										<?php endif; ?>
									</div>
								</div>
							</div>
							<?php
						}
						?>
					</div>
				</div>
			</div>
		</div>
	</div>
	<?php
}

/**
 * Storzen shop-slider prev/next icons (thin chevrons, matches shop-details.html; no Font Awesome dependency).
 *
 * @param string $direction 'prev' or 'next'.
 * @return string
 */
function storzen_shop_slider_nav_icon_svg($direction)
{
	$direction = 'next' === $direction ? 'next' : 'prev';
	// Same paths as Font Awesome 6 chevron-left / chevron-right (shop-details.html uses fa-solid).
	if ('next' === $direction) {
		$svg = '<svg class="szp-slider-nav-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 512" aria-hidden="true"><path fill="currentColor" d="M310.6 233.4c12.5 12.5 12.5 32.8 0 45.3l-192 192c-12.5 12.5-32.8 12.5-45.3 0s-12.5-32.8 0-45.3L242.7 256 73.4 86.6c-12.5-12.5-12.5-32.8 0-45.3s32.8-12.5 45.3 0l192 192z"/></svg>';
	} else {
		$svg = '<svg class="szp-slider-nav-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 512" aria-hidden="true"><path fill="currentColor" d="M9.4 233.4c-12.5 12.5-12.5 32.8 0 45.3l192 192c12.5 12.5 32.8 12.5 45.3 0s12.5-32.8 0-45.3L77.3 256 246.6 86.6c12.5-12.5 12.5-32.8 0-45.3s-32.8-12.5-45.3 0l-192 192z"/></svg>';
	}
	return $svg;
}

/**
 * Product IDs for the related slider: WooCommerce "related" first, then same-category products to reach $limit.
 *
 * wc_get_related_products() often returns fewer than $limit (tag/category overlap). Category fill matches
 * "show more from this shelf" when the catalog has products in the current product's categories.
 *
 * @param int $product_id Current product ID.
 * @param int $limit      Max products (1–24).
 * @return int[]
 */
function storzen_get_related_product_ids_for_slider($product_id, $limit)
{
	$product_id = absint($product_id);
	$limit      = max(1, min(24, (int) $limit));
	$ids        = array();
	if (function_exists('wc_get_related_products')) {
		$ids = wc_get_related_products($product_id, $limit);
	} elseif (function_exists('wc_get_product')) {
		$p = wc_get_product($product_id);
		if ($p) {
			$ids = $p->get_related($limit);
		}
	}
	$ids = array_values(array_unique(array_map('absint', (array) $ids)));
	if (count($ids) >= $limit) {
		return array_slice($ids, 0, $limit);
	}
	if (! function_exists('wc_get_product_term_ids') || ! function_exists('wc_get_products')) {
		return $ids;
	}
	$cat_ids = wc_get_product_term_ids($product_id, 'product_cat');
	if (empty($cat_ids)) {
		return $ids;
	}
	$slugs = array();
	foreach ($cat_ids as $tid) {
		$term = get_term(absint($tid), 'product_cat');
		if ($term && ! is_wp_error($term)) {
			$slugs[] = $term->slug;
		}
	}
	$slugs = array_unique(array_filter($slugs));
	if (empty($slugs)) {
		return $ids;
	}
	$needed  = $limit - count($ids);
	$exclude = array_merge(array($product_id), $ids);
	// phpcs:disable WordPressVIPMinimum.Performance.WPQueryParams.PostNotIn_exclude
	// Intentional: 'exclude' fills remaining related-product slots and must
	// exclude already-fetched IDs to avoid duplicates. List is always small.
	$more    = wc_get_products(
		array(
			'limit'    => $needed,
			'exclude'  => $exclude,
			'status'   => 'publish',
			'return'   => 'ids',
			'orderby'  => 'rand',
			'category' => $slugs,
		)
	);
	// phpcs:enable WordPressVIPMinimum.Performance.WPQueryParams.PostNotIn_exclude
	if (empty($more)) {
		return $ids;
	}
	$merged = array_merge($ids, array_map('absint', (array) $more));
	$merged = array_values(array_unique($merged));
	return array_slice($merged, 0, $limit);
}

/**
 * Related products block: shop-details.html shop-section + swiper shop-slider + shop-card-items.
 *
 * @param \WC_Product $product Product.
 * @param int         $limit   Max products.
 */
function storzen_print_related_products_slider($product, $limit)
{
	if (! $product instanceof \WC_Product) {
		return;
	}
	$limit       = max(1, min(24, (int) $limit));
	$related_ids = storzen_get_related_product_ids_for_slider($product->get_id(), $limit);
	if (empty($related_ids)) {
		return;
	}

	$slider_settings = array(
		'loop'        => true,
		'enable_nav'  => true,
		'enable_dots' => true,
		'smart_speed' => 700,
		'items'       => 4,
		'margin'      => 30,
	);
	global $product;
	$previous_product = $product instanceof \WC_Product ? $product : null;
?>
	<section class="shop-section fix section-padding szp-related-products-section related products">
		<div class="wco-container">
			<h1 class="style-heading"><?php esc_html_e('related Products', 'storzen'); ?></h1>
			<div class="swiper shop-slider szp-related-shop-slider" data-slider-settings="<?php echo esc_attr(wp_json_encode($slider_settings)); ?>">
				<div class="swiper-wrapper">
					<?php
					foreach ($related_ids as $rid) {
						$rel = wc_get_product($rid);
						if (! $rel || ! $rel->is_visible()) {
							continue;
						}
						$img_src = wp_get_attachment_image_src($rel->get_image_id(), 'full');
						$product = $rel;
					?>
						<div class="swiper-slide">
							<div class="shop-card-items mt-0">
								<div class="image">
									<div class="badges">
										<?php storzen_display_percentage_on_sale_badge(); ?>
										<?php
										$newness_days = 30;
										$created      = strtotime($rel->get_date_created());
										if ((time() - (60 * 60 * 24 * $newness_days)) < $created) {
											echo '<span class="bgc-black">' . esc_html__('hot', 'storzen') . '</span>';
										}
										?>
									</div>
									<a href="<?php echo esc_url($rel->get_permalink()); ?>">
										<?php
										// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- get_image() returns escaped HTML
										echo $rel->get_image('woocommerce_thumbnail');
										?>
									</a>
									<div class="social-style-two">
										<?php echo storzen_add_to_cart_button(); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- storzen_add_to_cart_button() returns markup already escaped with esc_url/esc_attr/wc_implode_html_attributes. ?>
										<?php storzen_product_card_compare_wishlist_icons($rel->get_id()); ?>
										<?php
										if (! empty($img_src[0])) {
											printf(
												'<a class="swp-image-popup" href="%s"><i class="far fa-eye"></i></a>',
												esc_url($img_src[0])
											);
										}
										?>
									</div>
								</div>
								<div class="content">
									<?php
									$cats = get_the_terms($rel->get_id(), 'product_cat');
									if ($cats && ! is_wp_error($cats)) {
										$cat = array_shift($cats);
										printf(
											'<a href="%s" class="category">%s</a>',
											esc_url(get_term_link($cat)),
											esc_html($cat->name)
										);
									}
									?>
									<h6>
										<a href="<?php echo esc_url($rel->get_permalink()); ?>" class="woo-title"><?php echo esc_html($rel->get_name()); ?></a>
									</h6>
									<div class="prices"><?php echo wp_kses_post($rel->get_price_html()); ?></div>
								</div>
							</div>
						</div>
					<?php
					}
					?>
				</div>
				<div class="swiper-dot text-center mt-5">
					<div class="dot"></div>
				</div>
			</div>
			<div class="array-button">
				<button type="button" class="array-prev" aria-label="<?php esc_attr_e('Previous', 'storzen'); ?>">
					<?php
					// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- storzen_kses_svg() sanitizes SVG output
					echo storzen_kses_svg(storzen_shop_slider_nav_icon_svg('prev'));
					?>
				</button>
				<button type="button" class="array-next" aria-label="<?php esc_attr_e('Next', 'storzen'); ?>">
					<?php
					// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- storzen_kses_svg() sanitizes SVG output
					echo storzen_kses_svg(storzen_shop_slider_nav_icon_svg('next'));
					?>
				</button>
			</div>
		</div>
	</section>
<?php
	if ($previous_product instanceof \WC_Product) {
		$product = $previous_product;
	}
}

/**
 * Detect blocks that output Related Products (WooCommerce block editor + query loop).
 *
 * @param array $parsed_block Block array.
 * @return bool
 */
function storzen_block_outputs_related_products($parsed_block)
{
	if (empty($parsed_block['blockName'])) {
		return false;
	}
	$name = $parsed_block['blockName'];
	if ('woocommerce/related-products' === $name) {
		return true;
	}
	if ('core/pattern' === $name && ! empty($parsed_block['attrs']['slug']) && false !== strpos((string) $parsed_block['attrs']['slug'], 'related-products')) {
		return true;
	}
	if ('core/query' === $name && ! empty($parsed_block['attrs']['namespace']) && 'woocommerce/related-products' === $parsed_block['attrs']['namespace']) {
		return true;
	}
	if ('woocommerce/product-collection' === $name) {
		$encoded = wp_json_encode($parsed_block['attrs'] ?? array());
		if ($encoded && preg_match('/woocommerce\/product-collection\/related|relatedProductReference/', $encoded)) {
			return true;
		}
	}
	return false;
}

/**
 * Strip Related Products blocks at render time (catches variations parse_blocks may miss).
 *
 * @param string $block_content HTML.
 * @param array  $parsed_block  Block.
 * @return string
 */
function storzen_render_block_suppress_related_in_product_description($block_content, $parsed_block)
{
	if (storzen_block_outputs_related_products($parsed_block)) {
		return '';
	}
	return $block_content;
}

/**
 * Remove Related Products from block/shortcode data so they do not render inside the Description tab.
 *
 * @param string $content Post content.
 * @return string
 */
function storzen_strip_related_from_product_description($content)
{
	$content = (string) $content;
	if ($content === '') {
		return $content;
	}
	if (function_exists('has_blocks') && has_blocks($content) && function_exists('parse_blocks') && function_exists('serialize_blocks')) {
		$blocks  = parse_blocks($content);
		$blocks  = storzen_strip_related_product_blocks_recursive($blocks);
		$content = serialize_blocks($blocks);
	}
	$content = preg_replace('/\[\s*related_products(?:\s[^\]]*)?\]/is', '', $content);
	return $content;
}

/**
 * @param array $blocks Parsed blocks.
 * @return array
 */
function storzen_strip_related_product_blocks_recursive($blocks)
{
	if (! is_array($blocks)) {
		return array();
	}
	$out = array();
	foreach ($blocks as $block) {
		if (storzen_block_outputs_related_products($block)) {
			continue;
		}
		$name = isset($block['blockName']) ? $block['blockName'] : '';
		if ('core/heading' === $name && ! empty($block['innerHTML'])) {
			$htext = trim(wp_strip_all_tags($block['innerHTML']));
			if (preg_match('/^related\s*products$/i', $htext)) {
				continue;
			}
		}
		if (! empty($block['innerBlocks'])) {
			$block['innerBlocks'] = storzen_strip_related_product_blocks_recursive($block['innerBlocks']);
		}
		$out[] = $block;
	}
	return $out;
}

/**
 * Regex pass for Related Products fragments (fast path before DOM).
 *
 * @param string $html HTML.
 * @return string
 */
function storzen_strip_related_html_from_description_output_regex($html)
{
	$html = (string) $html;
	if ($html === '') {
		return $html;
	}

	$prev  = '';
	$guard = 0;
	while ($prev !== $html && $guard < 25) {
		$prev   = $html;
		$guard += 1;
		// Storzen Related Products slider (non-greedy can fail if inner cards contain </section>; DOM pass fixes that).
		$html = preg_replace('/<section[^>]*\bszp-related-products-section\b[^>]*>[\s\S]*?<\/section>/is', '', $html);
		$html = preg_replace('/<section[^>]*class="[^"]*(?:\brelated\b[^"]*\bproducts\b|\bproducts\b[^"]*\brelated\b)[^"]*"[^>]*>[\s\S]*?<\/section>/is', '', $html);
	}

	$html = preg_replace('/<h2[^>]*>\s*Related\s*products\s*<\/h2>[\s\S]*$/is', '', $html);
	$html = preg_replace('/<h[123][^>]*>\s*Related\s*Products\s*<\/h[123]>[\s\S]*$/is', '', $html);
	$html = preg_replace('/<h1[^>]*class="[^"]*\bstyle-heading\b[^"]*"[^>]*>\s*related\s*Products\s*<\/h1>[\s\S]*$/is', '', $html);

	return $html;
}

/**
 * Whether a DOM element is a Related Products UI block (WooCommerce + Storzen).
 *
 * @param \DOMElement $el Element.
 * @return bool
 */
function storzen_dom_element_is_related_products_block($el)
{
	if (! $el instanceof \DOMElement) {
		return false;
	}
	$class = $el->getAttribute('class');
	if ($class === '') {
		return false;
	}
	$classes = array_filter(preg_split('/\s+/', $class, -1, PREG_SPLIT_NO_EMPTY));
	if (in_array('szp-related-products-section', $classes, true)) {
		return true;
	}
	if (in_array('wp-block-woocommerce-related-products', $classes, true)) {
		return true;
	}
	// Standard WC loop + Storzen slider wrapper.
	if (in_array('related', $classes, true) && in_array('products', $classes, true)) {
		return true;
	}

	return false;
}

/**
 * Strip Related Products markup using DOM (handles nested </section> and non-section wrappers).
 *
 * @param string $html HTML.
 * @return string
 */
function storzen_strip_related_html_from_description_output_dom($html)
{
	$html = (string) $html;
	if ($html === '' || ! class_exists('DOMDocument')) {
		return $html;
	}

	libxml_use_internal_errors(true);
	$dom = new \DOMDocument();
	$wid = 'wcp' . substr(md5($html), 0, 12);
	$dom->loadHTML('<?xml encoding="utf-8" ?><div id="' . $wid . '">' . $html . '</div>', LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD);

	$xpath = new \DOMXPath($dom);
	$wrap  = $xpath->query('//*[@id="' . $wid . '"]')->item(0);
	if (! $wrap) {
		libxml_clear_errors();
		return $html;
	}

	$to_remove = array();
	foreach ($xpath->query('.//*[@class]', $wrap) as $node) {
		if (storzen_dom_element_is_related_products_block($node)) {
			$to_remove[] = $node;
		}
	}
	// Remove deepest nodes first so we do not orphan a child before its parent is removed.
	foreach (array_reverse($to_remove) as $node) {
		if ($node->parentNode) {
			$node->parentNode->removeChild($node);
		}
	}

	$out = '';
	foreach ($wrap->childNodes as $child) {
		$out .= $dom->saveHTML($child);
	}
	libxml_clear_errors();
	return $out;
}

/**
 * Strip Related Products markup from rendered description HTML (blocks, shortcodes, or filters on the_content).
 *
 * Runs after apply_filters( 'the_content' // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- Intentionally reusing WP core filter for content processing. ) so anything appended by third-party filters is removed too.
 *
 * @param string $html Rendered HTML.
 * @return string
 */
function storzen_strip_related_html_from_description_output($html)
{
	$html = (string) $html;
	if ($html === '') {
		return $html;
	}

	$regex_out = storzen_strip_related_html_from_description_output_regex($html);
	$dom_out   = storzen_strip_related_html_from_description_output_dom($regex_out);
	// If DOM emptied output but regex still had text, DOM parse likely failed — keep regex result.
	if ('' === $dom_out && '' !== trim(wp_strip_all_tags($regex_out))) {
		return $regex_out;
	}

	return $dom_out;
}

/**
 * Whether this tab is the long product description (same content as WC description tab).
 *
 * @param string $key Tab key.
 * @param array  $tab Tab args.
 * @return bool
 */
function storzen_product_tab_is_long_description($key, $tab)
{
	if ('description' === $key) {
		return true;
	}
	if (empty($tab['callback']) || ! is_array($tab)) {
		return false;
	}
	$cb = $tab['callback'];
	if (is_string($cb) && 'woocommerce_product_description_tab' === $cb) {
		return true;
	}
	if (is_array($cb) && ! empty($cb[1]) && is_string($cb[1]) && 'woocommerce_product_description_tab' === $cb[1]) {
		return true;
	}
	return false;
}

/**
 * Description tab: long description only (no Related Products block/shortcode embedded in content).
 *
 * @param string $key Tab key.
 * @param array  $tab Tab args.
 */
function storzen_render_description_tab_without_related($key, $tab)
{
	$product_id = (int) get_the_ID();
	if (! $product_id) {
		return;
	}
	$product_post = get_post($product_id);
	if (! $product_post instanceof \WP_Post || 'product' !== $product_post->post_type) {
		return;
	}

	// phpcs:ignore Squiz.PHP.DisallowMultipleAssignments.Found -- intentional save/restore for Elementor.
	$previous_post = isset($GLOBALS['post']) && $GLOBALS['post'] instanceof \WP_Post ? $GLOBALS['post'] : null;
	$GLOBALS['post'] = $product_post;
	setup_postdata($product_post);

	// phpcs:ignore WordPress.WP.I18n.TextDomainMismatch -- Using WooCommerce's own translations
	$heading = apply_filters('woocommerce_product_description_heading', __('Description', 'woocommerce'));

	if ($heading) {
		echo '<h2>' . esc_html($heading) . '</h2>';
	}

	$content = storzen_strip_related_from_product_description($product_post->post_content);

	add_filter('render_block', 'storzen_render_block_suppress_related_in_product_description', 10, 2);
	try {
		// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- same as WC description.php (the_content filters).
		// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- Intentionally reusing WP core filter for content processing.
		$html = apply_filters('the_content', $content);
		$html = storzen_strip_related_html_from_description_output($html);
		// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Content filtered through the_content which handles escaping
		echo $html;
	} finally {
		remove_filter('render_block', 'storzen_render_block_suppress_related_in_product_description', 10);
		if ($previous_post instanceof \WP_Post) {
			$GLOBALS['post'] = $previous_post;
			setup_postdata($previous_post);
		} else {
			wp_reset_postdata();
		}
	}
}

/**
 * Replace default description tab callback so Related Products never appear inside this tab.
 *
 * @param array $tabs Tabs.
 * @return array
 */
function storzen_product_tabs_description_strip_related($tabs)
{
	if (isset($tabs['description'])) {
		$tabs['description']['callback'] = 'storzen_render_description_tab_without_related';
	}
	return $tabs;
}

add_filter('woocommerce_product_tabs', 'storzen_product_tabs_description_strip_related', 99999);
