<?php
/**
 * Product Gallery Widget - Render functions
 *
 * All functions are prefixed ggmc_product_gallery_ and operate on plain values
 * passed in as arguments. No class state is used.
 */

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

/**
 * Main render entry-point.
 *
 * Resolves the product and images, then dispatches to the appropriate layout
 * renderer.
 *
 * @param \GGM_Commerce_Product_Gallery_Widget $widget The widget instance.
 */
function ggmc_product_gallery_render( $widget ) {
	$settings = $widget->get_settings_for_display();

	// Resolve product ID.
	$data_handler = new GGM_Commerce_Product_Gallery_Data_Handler();

	$product_id = null;
	if ( isset( $settings['product_source'] ) && $settings['product_source'] === 'custom' ) {
		$product_id = ! empty( $settings['product_id'] ) ? absint( $settings['product_id'] ) : null;
	}

	if ( empty( $product_id ) ) {
		$product_id = $data_handler->get_current_product_id();
	}

	$product = $data_handler->get_product( $product_id );
	$images  = $data_handler->get_product_images( $product_id );

	if ( empty( $images ) ) {
		ggmc_product_gallery_render_empty_state();
		return;
	}

	$layout = $settings['gallery_layout'] ?? 'standard';

	switch ( $layout ) {
		case 'slider':
			ggmc_product_gallery_render_slider( $product, $images, $settings, $data_handler );
			break;
		case 'grid':
			ggmc_product_gallery_render_grid( $product, $images, $settings, $data_handler );
			break;
		case 'standard':
		default:
			ggmc_product_gallery_render_standard( $product, $images, $settings, $data_handler );
			break;
	}
}

// ── Layout renderers ──────────────────────────────────────────────────────────

/**
 * Render standard layout (main image + thumbnails).
 *
 * @param object                                      $product      Product row object.
 * @param array                                       $images       Array of image objects.
 * @param array                                       $settings     Widget settings.
 * @param GGM_Commerce_Product_Gallery_Data_Handler   $data_handler Data handler instance.
 */
function ggmc_product_gallery_render_standard( $product, $images, $settings, $data_handler ) {
	ggmc_product_gallery_render_wrapper_start( $product, $images, $settings, $data_handler );
	ggmc_product_gallery_render_main_image( $images[0], $settings );
	ggmc_product_gallery_render_thumbnails( $images, $settings, $data_handler );
	ggmc_product_gallery_render_wrapper_end();
	ggmc_product_gallery_render_lightbox_markup( $product, $images, $settings, $data_handler );
	ggmc_product_gallery_render_styles( $settings );
}

/**
 * Render slider layout.
 *
 * @param object                                      $product      Product row object.
 * @param array                                       $images       Array of image objects.
 * @param array                                       $settings     Widget settings.
 * @param GGM_Commerce_Product_Gallery_Data_Handler   $data_handler Data handler instance.
 */
function ggmc_product_gallery_render_slider( $product, $images, $settings, $data_handler ) {
	$slider_config = [
		'autoplay'        => $settings['slider_autoplay'] === 'yes',
		'autoplaySpeed'   => intval( $settings['slider_autoplay_speed'] ?? 3000 ),
		'pauseOnHover'    => ( $settings['slider_pause_on_hover'] ?? 'yes' ) === 'yes',
		'loop'            => ( $settings['slider_loop'] ?? 'yes' ) === 'yes',
		'showArrows'      => ( $settings['slider_show_arrows'] ?? 'yes' ) === 'yes',
		'showDots'        => ( $settings['slider_show_dots'] ?? 'yes' ) === 'yes',
		'transition'      => $settings['slider_transition'] ?? 'slide',
		'transitionSpeed' => intval( $settings['slider_transition_speed'] ?? 500 ),
		'totalImages'     => count( $images ),
	];

	$classes = [ 'ggm-gallery-slider' ];
	if ( $slider_config['showArrows'] ) {
		$classes[] = 'has-arrows';
	}
	if ( $slider_config['showDots'] ) {
		$classes[] = 'has-dots';
	}
	if ( $settings['enable_lightbox'] === 'yes' ) {
		$classes[] = 'has-lightbox';
	}

	echo '<div class="' . esc_attr( implode( ' ', $classes ) ) . '" ';
	echo 'data-slider-config="' . esc_attr( wp_json_encode( $slider_config ) ) . '" ';
	echo 'data-lightbox="' . esc_attr( $settings['enable_lightbox'] === 'yes' ? 'true' : 'false' ) . '">';

	// Slider track.
	echo '<div class="ggm-slider-track">';
	foreach ( $images as $index => $image ) {
		ggmc_product_gallery_render_slider_slide( $image, $index, $settings );
	}
	echo '</div>';

	// Navigation arrows.
	if ( $slider_config['showArrows'] ) {
		echo '<button class="ggm-slider-nav ggm-slider-prev" aria-label="Previous slide">';
		echo '<span class="dashicons dashicons-arrow-left-alt2"></span>';
		echo '</button>';
		echo '<button class="ggm-slider-nav ggm-slider-next" aria-label="Next slide">';
		echo '<span class="dashicons dashicons-arrow-right-alt2"></span>';
		echo '</button>';
	}

	// Dots navigation.
	if ( $slider_config['showDots'] ) {
		echo '<div class="ggm-slider-dots">';
		foreach ( $images as $index => $image ) {
			$active = $index === 0 ? ' active' : '';
			echo '<button class="ggm-slider-dot' . esc_attr( $active ) . '" data-index="' . esc_attr( $index ) . '" aria-label="Go to slide ' . esc_attr( $index + 1 ) . '"></button>';
		}
		echo '</div>';
	}

	echo '</div>';

	ggmc_product_gallery_render_lightbox_markup( $product, $images, $settings, $data_handler );
}

/**
 * Render a single slider slide.
 *
 * @param object $image    Image object.
 * @param int    $index    Zero-based slide index.
 * @param array  $settings Widget settings.
 */
function ggmc_product_gallery_render_slider_slide( $image, $index, $settings ) {
	$active = $index === 0 ? ' active' : '';

	echo '<div class="ggm-slider-slide' . esc_attr( $active ) . '" data-index="' . esc_attr( $index ) . '">';

	$img_attributes = [
		'alt'              => esc_attr( $image->alt ?: $image->title ),
		'data-image-index' => $index,
	];

	if ( $settings['enable_lightbox'] === 'yes' ) {
		$img_attributes['data-lightbox-url'] = esc_url( $image->full_url );
		$img_attributes['style']             = 'cursor: pointer;';
	}

	// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- ggmc_product_gallery_get_image_html() returns pre-escaped HTML
	echo ggmc_product_gallery_get_image_html( $image, 'large', $img_attributes );
	echo '</div>';
}

/**
 * Render grid layout.
 *
 * @param object                                      $product      Product row object.
 * @param array                                       $images       Array of image objects.
 * @param array                                       $settings     Widget settings.
 * @param GGM_Commerce_Product_Gallery_Data_Handler   $data_handler Data handler instance.
 */
function ggmc_product_gallery_render_grid( $product, $images, $settings, $data_handler ) {
	$image_ratio    = $settings['grid_image_ratio'] ?? 'square';
	$first_featured = ( $settings['grid_first_image_featured'] ?? 'no' ) === 'yes';
	$featured_span  = $settings['grid_featured_span'] ?? '2x2';

	$classes = [ 'ggm-gallery-grid', 'ratio-' . $image_ratio ];
	if ( $first_featured ) {
		$classes[] = 'first-featured';
		$classes[] = 'featured-span-' . $featured_span;
	}
	if ( $settings['enable_lightbox'] === 'yes' ) {
		$classes[] = 'has-lightbox';
	}

	echo '<div class="' . esc_attr( implode( ' ', $classes ) ) . '" ';
	echo 'data-lightbox="' . esc_attr( $settings['enable_lightbox'] === 'yes' ? 'true' : 'false' ) . '">';

	foreach ( $images as $index => $image ) {
		ggmc_product_gallery_render_grid_item( $image, $index, $settings, $first_featured && $index === 0 );
	}

	echo '</div>';

	ggmc_product_gallery_render_lightbox_markup( $product, $images, $settings, $data_handler );
}

/**
 * Render a single grid item.
 *
 * @param object $image       Image object.
 * @param int    $index       Zero-based item index.
 * @param array  $settings    Widget settings.
 * @param bool   $is_featured Whether this item is the featured (first) item.
 */
function ggmc_product_gallery_render_grid_item( $image, $index, $settings, $is_featured = false ) {
	$classes = [ 'ggm-grid-item' ];
	if ( $is_featured ) {
		$classes[] = 'featured';
	}

	echo '<div class="' . esc_attr( implode( ' ', $classes ) ) . '" data-index="' . esc_attr( $index ) . '">';

	$img_attributes = [
		'alt'              => esc_attr( $image->alt ?: $image->title ),
		'data-image-index' => $index,
	];

	if ( $settings['enable_lightbox'] === 'yes' ) {
		$img_attributes['data-lightbox-url'] = esc_url( $image->full_url );
		$img_attributes['style']             = 'cursor: pointer;';
	}

	// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- ggmc_product_gallery_get_image_html() returns pre-escaped HTML
	echo ggmc_product_gallery_get_image_html( $image, $is_featured ? 'large' : 'medium', $img_attributes );
	echo '</div>';
}

// ── Standard layout helpers ───────────────────────────────────────────────────

/**
 * Render the opening wrapper div for the standard gallery layout.
 *
 * @param object                                      $product      Product row object.
 * @param array                                       $images       Array of image objects.
 * @param array                                       $settings     Widget settings.
 * @param GGM_Commerce_Product_Gallery_Data_Handler   $data_handler Data handler instance.
 */
function ggmc_product_gallery_render_wrapper_start( $product, $images, $settings, $data_handler ) {
	$thumbnail_position      = $settings['thumbnail_position'] ?? 'bottom';
	$max_visible_thumbnails  = intval( $settings['max_visible_thumbnails'] ?? 4 );
	$total_images            = count( $images );
	$show_navigation         = $total_images > $max_visible_thumbnails;

	$classes = [
		'ggm-product-gallery-container',
		'thumbnails-pos-' . esc_attr( $thumbnail_position ),
	];

	if ( $show_navigation ) {
		$classes[] = 'has-navigation';
	}
	if ( $settings['enable_lightbox'] === 'yes' ) {
		$classes[] = 'has-lightbox';
	}
	if ( $settings['enable_zoom'] === 'yes' ) {
		$classes[] = 'has-zoom';
	}
	if ( $settings['enable_lazy_loading'] === 'yes' ) {
		$classes[] = 'has-lazy-loading';
	}

	$variation_images = ggmc_product_gallery_build_variation_images_data( $product, $images );
	$product_id       = isset( $product->id ) ? (int) $product->id : 0;

	echo '<div class="' . esc_attr( implode( ' ', $classes ) ) . '" ';
	echo 'data-product-id="' . esc_attr( $product_id ) . '" ';
	echo 'data-max-visible="' . esc_attr( $max_visible_thumbnails ) . '" ';
	echo 'data-total-images="' . esc_attr( $total_images ) . '" ';
	echo 'data-lightbox="' . esc_attr( $settings['enable_lightbox'] === 'yes' ? 'true' : 'false' ) . '" ';
	echo 'data-zoom="' . esc_attr( $settings['enable_zoom'] === 'yes' ? 'true' : 'false' ) . '" ';
	echo 'data-zoom-level="' . esc_attr( $settings['zoom_level']['size'] ?? 2 ) . '" ';
	echo 'data-lazy-loading="' . esc_attr( $settings['enable_lazy_loading'] === 'yes' ? 'true' : 'false' ) . '" ';
	echo 'data-preloading="' . esc_attr( $settings['enable_preloading'] === 'yes' ? 'true' : 'false' ) . '" ';
	echo "data-variation-images='" . esc_attr( wp_json_encode( $variation_images ) ) . "'>";
}

/**
 * Render the closing wrapper div.
 */
function ggmc_product_gallery_render_wrapper_end() {
	echo '</div>';
}

/**
 * Render the main (featured) image.
 *
 * @param object $first_image The first image object in the gallery.
 * @param array  $settings    Widget settings.
 */
function ggmc_product_gallery_render_main_image( $first_image, $settings ) {
	$enable_zoom        = $settings['enable_zoom'] === 'yes';
	$enable_lightbox    = $settings['enable_lightbox'] === 'yes';
	$image_hover_effect = $settings['image_hover_effect'] ?? 'none';

	$main_image_classes = [ 'ggm-main-image' ];

	if ( $enable_zoom ) {
		$main_image_classes[] = 'ggm-zoom-container';
	}
	if ( $image_hover_effect !== 'none' ) {
		$main_image_classes[] = 'hover-' . $image_hover_effect;
	}

	echo '<div class="' . esc_attr( implode( ' ', $main_image_classes ) ) . '">';

	$image_attributes = [
		'data-image-index' => '0',
		'alt'              => esc_attr( $first_image->alt ?: $first_image->title ),
	];

	if ( $enable_lightbox ) {
		$image_attributes['data-lightbox-url'] = esc_url( $first_image->full_url );
		$image_attributes['style']             = 'cursor: pointer;';
	}
	if ( $enable_zoom ) {
		$image_attributes['data-zoom-url'] = esc_url( $first_image->full_url );
	}

	// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- ggmc_product_gallery_get_image_html() returns pre-escaped HTML
	echo ggmc_product_gallery_get_image_html( $first_image, 'large', $image_attributes );
	echo '</div>';
}

/**
 * Render the thumbnails strip.
 *
 * @param array                                       $images       Array of image objects.
 * @param array                                       $settings     Widget settings.
 * @param GGM_Commerce_Product_Gallery_Data_Handler   $data_handler Data handler instance.
 */
function ggmc_product_gallery_render_thumbnails( $images, $settings, $data_handler ) {
	$max_visible_thumbnails = intval( $settings['max_visible_thumbnails'] ?? 4 );
	$total_images           = count( $images );
	$show_navigation        = $total_images > $max_visible_thumbnails;

	echo '<div class="ggm-gallery-thumbnails-wrapper">';

	if ( $show_navigation ) {
		ggmc_product_gallery_render_navigation_buttons();
	}

	echo '<div class="ggm-gallery-thumbnails" data-current-offset="0">';

	foreach ( $images as $index => $image ) {
		ggmc_product_gallery_render_thumbnail( $image, $index, $settings, $data_handler );
	}

	echo '</div>';
	echo '</div>';
}

/**
 * Render prev/next thumbnail navigation buttons.
 */
function ggmc_product_gallery_render_navigation_buttons() {
	echo '<button class="ggm-gallery-nav ggm-gallery-prev" aria-label="Previous thumbnails">';
	echo '<span class="dashicons dashicons-arrow-left-alt2"></span>';
	echo '</button>';

	echo '<button class="ggm-gallery-nav ggm-gallery-next" aria-label="Next thumbnails">';
	echo '<span class="dashicons dashicons-arrow-right-alt2"></span>';
	echo '</button>';
}

/**
 * Render a single thumbnail item.
 *
 * @param object                                      $image        Image object.
 * @param int                                         $index        Zero-based thumbnail index.
 * @param array                                       $settings     Widget settings.
 * @param GGM_Commerce_Product_Gallery_Data_Handler   $data_handler Data handler instance.
 */
function ggmc_product_gallery_render_thumbnail( $image, $index, $settings, $data_handler ) {
	$max_visible_thumbnails = intval( $settings['max_visible_thumbnails'] ?? 4 );
	$should_lazy_load       = $data_handler->should_lazy_load( $index, $max_visible_thumbnails, $settings['enable_lazy_loading'] === 'yes' );

	$thumbnail_classes = [ 'ggm-gallery-thumbnail' ];
	if ( $index === 0 ) {
		$thumbnail_classes[] = 'active';
	}
	if ( $should_lazy_load ) {
		$thumbnail_classes[] = 'lazy-load';
	}

	echo '<div class="' . esc_attr( implode( ' ', $thumbnail_classes ) ) . '" ';
	echo 'data-image-index="' . esc_attr( $index ) . '" ';
	echo 'data-full-src="' . esc_url( $image->large_url ) . '" ';
	echo 'data-lightbox-url="' . esc_url( $image->full_url ) . '" ';

	if ( $settings['enable_zoom'] === 'yes' ) {
		echo 'data-zoom-url="' . esc_url( $image->full_url ) . '" ';
	}

	if ( ! empty( $image->large_srcset ) ) {
		echo 'data-full-srcset="' . esc_attr( $image->large_srcset ) . '" ';
	}

	echo '>';

	$thumbnail_attributes = [
		'alt' => esc_attr( $image->alt ?: $image->title ),
	];

	if ( $should_lazy_load ) {
		$thumbnail_attributes['src']      = $data_handler->get_lazy_placeholder();
		$thumbnail_attributes['data-src'] = $image->thumbnail_url;
		$thumbnail_attributes['loading']  = 'lazy';
	}

	// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- ggmc_product_gallery_get_image_html() returns pre-escaped HTML
	echo ggmc_product_gallery_get_image_html( $image, 'thumbnail', $thumbnail_attributes, $should_lazy_load );
	echo '</div>';
}

// ── Lightbox ──────────────────────────────────────────────────────────────────

/**
 * Render the hidden lightbox markup (shared across all layouts).
 *
 * @param object                                      $product      Product row object.
 * @param array                                       $images       Array of image objects.
 * @param array                                       $settings     Widget settings.
 * @param GGM_Commerce_Product_Gallery_Data_Handler   $data_handler Data handler instance.
 */
function ggmc_product_gallery_render_lightbox_markup( $product, $images, $settings, $data_handler ) {
	if ( $settings['enable_lightbox'] !== 'yes' ) {
		return;
	}

	$lightbox_config = $data_handler->get_lightbox_config( $product, $images, $settings );

	echo '<div class="ggm-gallery-lightbox" style="display: none;">';
	echo '<div class="ggm-lightbox-overlay"></div>';
	echo '<div class="ggm-lightbox-container">';

	// Close button.
	echo '<button class="ggm-lightbox-close" aria-label="Close lightbox">';
	echo '<span class="dashicons dashicons-no-alt"></span>';
	echo '</button>';

	// Navigation arrows.
	echo '<button class="ggm-lightbox-nav ggm-lightbox-prev" aria-label="Previous image">';
	echo '<span class="dashicons dashicons-arrow-left-alt2"></span>';
	echo '</button>';

	echo '<button class="ggm-lightbox-nav ggm-lightbox-next" aria-label="Next image">';
	echo '<span class="dashicons dashicons-arrow-right-alt2"></span>';
	echo '</button>';

	// Image container.
	echo '<div class="ggm-lightbox-image-container">';
	echo '<img class="ggm-lightbox-image" src="" alt="" />';
	echo '</div>';

	// Product info (if enabled).
	if ( $lightbox_config['show_info'] && ! empty( $lightbox_config['product_info'] ) ) {
		ggmc_product_gallery_render_lightbox_product_info( $lightbox_config['product_info'], $settings );
	}

	// Image counter.
	echo '<div class="ggm-lightbox-counter">';
	echo '<span class="current">1</span> / <span class="total">' . esc_html( count( $images ) ) . '</span>';
	echo '</div>';

	echo '</div>';
	echo '</div>';
}

/**
 * Render product info panel inside the lightbox.
 *
 * @param array $product_info Associative array of product info fields.
 * @param array $settings     Widget settings.
 */
function ggmc_product_gallery_render_lightbox_product_info( $product_info, $settings ) {
	$info_position = $settings['lightbox_info_position'] ?? 'bottom';

	echo '<div class="ggm-lightbox-product-info position-' . esc_attr( $info_position ) . '">';

	if ( ! empty( $product_info['title'] ) ) {
		echo '<h3 class="product-title">' . esc_html( $product_info['title'] ) . '</h3>';
	}

	if ( ! empty( $product_info['price'] ) ) {
		// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- price contains pre-formatted HTML from price formatting function
		echo '<div class="product-price">' . $product_info['price'] . '</div>';
	}

	if ( ! empty( $product_info['description'] ) ) {
		echo '<div class="product-description">' . wp_kses_post( $product_info['description'] ) . '</div>';
	}

	if ( ! empty( $product_info['stock'] ) ) {
		// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- stock contains pre-formatted HTML from stock status function
		echo '<div class="product-stock">' . $product_info['stock'] . '</div>';
	}

	echo '</div>';
}

// ── Dynamic styles ────────────────────────────────────────────────────────────

/**
 * Render any inline <style> block required for PHP-dynamic CSS selectors.
 *
 * Only outputs markup when image_hover_effect is not 'none', because the
 * hover selector depends on a runtime setting value.
 *
 * @param array $settings Widget settings.
 */
function ggmc_product_gallery_render_styles( $settings ) {
	$image_hover_effect = $settings['image_hover_effect'] ?? 'none';

	if ( $image_hover_effect === 'none' ) {
		return;
	}

	$image_hover_trigger = $settings['image_hover_trigger'] ?? 'image';
	$hover_selector      = $image_hover_trigger === 'container'
		? '.ggm-product-gallery-container:hover .ggm-main-image'
		: '.ggm-main-image:hover';
	?>
	<style>
		<?php // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- $hover_selector is a safe CSS selector built from sanitized settings ?>
		<?php echo $hover_selector; ?>.hover-zoom img {
			transform: scale(1.1);
		}

		<?php // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- $hover_selector is a safe CSS selector built from sanitized settings ?>
		<?php echo $hover_selector; ?>.hover-slide img {
			transform: translateX(-10px);
		}
	</style>
	<?php
}

// ── Shared utilities ──────────────────────────────────────────────────────────

/**
 * Build variation images data structure for JavaScript consumption.
 *
 * @param object $product Product row object.
 * @param array  $images  Array of image objects.
 * @return array Variation images data with 'parent' key (and optionally variation IDs).
 */
function ggmc_product_gallery_build_variation_images_data( $product, $images ) {
	$variation_images = [];

	$parent_images = [];
	foreach ( $images as $image ) {
		$parent_images[] = [
			'id'               => isset( $image->id ) ? (int) $image->id : 0,
			'url'              => $image->large_url ?? $image->url ?? '',
			'thumbnail_url'    => $image->thumbnail_url ?? $image->url ?? '',
			'large_url'        => $image->large_url ?? $image->url ?? '',
			'full_url'         => $image->full_url ?? $image->url ?? '',
			'thumbnail_srcset' => $image->thumbnail_srcset ?? '',
			'large_srcset'     => $image->large_srcset ?? '',
			'alt'              => $image->alt ?? '',
			'title'            => $image->title ?? '',
		];
	}
	$variation_images['parent'] = $parent_images;

	return $variation_images;
}

/**
 * Build an <img> element string with proper lazy-load handling.
 *
 * @param object $image      Image object (from data handler).
 * @param string $size       One of 'thumbnail', 'medium', 'large', 'full'.
 * @param array  $attributes Extra HTML attributes to merge.
 * @param bool   $is_lazy    Whether this image should be lazy-loaded.
 * @return string Escaped <img> HTML string.
 */
function ggmc_product_gallery_get_image_html( $image, $size = 'medium', $attributes = [], $is_lazy = false ) {
	$src    = '';
	$srcset = '';

	switch ( $size ) {
		case 'thumbnail':
			$src    = $image->thumbnail_url;
			$srcset = $image->thumbnail_srcset;
			break;
		case 'medium':
			$src    = $image->medium_url;
			$srcset = $image->medium_srcset;
			break;
		case 'large':
			$src    = $image->large_url;
			$srcset = $image->large_srcset;
			break;
		case 'full':
			$src    = $image->full_url;
			$srcset = $image->full_srcset;
			break;
	}

	$img_attributes = [];

	if ( $is_lazy && isset( $attributes['data-src'] ) ) {
		$img_attributes['src']      = $attributes['src']; // Placeholder.
		$img_attributes['data-src'] = $attributes['data-src']; // Real source.
		if ( ! empty( $srcset ) ) {
			$img_attributes['data-srcset'] = $srcset;
		}
	} else {
		$img_attributes['src'] = $src;
		if ( ! empty( $srcset ) ) {
			$img_attributes['srcset'] = $srcset;
			$img_attributes['sizes']  = '(max-width: 768px) 100vw, 50vw';
		}
	}

	// Merge with additional attributes (caller values win on conflict).
	$img_attributes = array_merge( $img_attributes, $attributes );

	$attr_string = '';
	foreach ( $img_attributes as $attr => $value ) {
		if ( $value !== '' ) {
			$attr_string .= ' ' . esc_attr( $attr ) . '="' . esc_attr( $value ) . '"';
		}
	}

	return '<img' . $attr_string . ' />';
}

/**
 * Render the empty-state placeholder shown when no images are available.
 */
function ggmc_product_gallery_render_empty_state() {
	echo '<div class="ggm-product-gallery-empty">';
	echo '<img src="' . esc_url( ggmc_get_placeholder_image_src() ) . '" alt="No images" />';
	echo '</div>';
}
