<?php
/**
 * Image-presentation builder.
 *
 * Pure-function builder that translates a §7.4 `ProductSnapshot`
 * plus a §5.2 `RenderContext` into a §10.4
 * {@see \Affilun\Application\Render\ViewModel\Parts\ImageSet}
 * the §8.2 ProductCardViewModel can carry verbatim. The mapping
 * follows `specifications/rendering-and-template-spec.md` §10.4
 * (alt rule + image-region branches), §12 (ImagePresentationBuilder
 * responsibilities + lazy-load policy), and §13.3 (graceful
 * degradation: malformed image URLs are silently skipped rather
 * than raising).
 *
 * Selection / dedup rules:
 *
 *   - The §10.4 "primary image を優先" rule maps the snapshot's
 *     {@see ProductSnapshot::primaryImage()} to the
 *     {@see ImageSet::primary()} slot. When the snapshot does not
 *     carry a primary image the builder returns an empty
 *     {@see ImageSet} (primary `null`, gallery `[]`); §13.3 forbids
 *     synthesizing a placeholder at this layer.
 *
 *   - Gallery entries from
 *     {@see ProductSnapshot::galleryImages()} are deduplicated in
 *     first-seen order, and the primary URL (when present) is
 *     stripped from the gallery list so the same image never
 *     renders twice.
 *
 *   - URLs that do not start with `http://` or `https://` are
 *     silently skipped per §13.3 ("画像なし" graceful
 *     degradation): the ViewModel layer never logs at this
 *     boundary because §23.1 forbids surfacing scheme-validation
 *     errors to end users.
 *
 *   - The §10.4 alt rule is "原則として商品タイトル": every
 *     {@see ImageView} (primary and gallery alike) carries
 *     {@see ProductSnapshot::title()} as its `alt`. Positional
 *     "image 2 of 5" auto-numbering is explicitly NOT performed
 *     at this layer per §10.4 — that is a renderer concern.
 *
 *   - `width` / `height` are always `null`: provider snapshots in
 *     Phase 5 do not carry intrinsic image dimensions; a future
 *     slice can extend the snapshot and read them here without
 *     changing this builder's signature.
 *
 *   - The §12.2 `loading` attribute follows surface-driven
 *     selection: AMP surfaces use `eager` (the AMP layout system
 *     handles lazy itself), the editor preview uses `eager` (the
 *     preview is small and visible on load), and the frontend
 *     respects {@see RenderContext::lazyLoadEnabled()} (`lazy`
 *     when true, `eager` when false).
 *
 * Instances carry no state and the {@see self::build()} method is
 * pure: same input, same output, no I/O, no globals.
 *
 * @package Affilun\Application\Render\Builder
 */

declare(strict_types=1);

namespace Affilun\Application\Render\Builder;

use Affilun\Application\Render\ViewModel\Parts\ImageSet;
use Affilun\Application\Render\ViewModel\Parts\ImageView;
use Affilun\Domain\Product\ProductSnapshot;
use Affilun\Domain\Render\RenderContext;

/**
 * Pure §10.4 / §12 image-presentation builder.
 *
 * See the file-level docblock for the mapping to
 * `rendering-and-template-spec.md` §10.4 / §12 / §13.3 and the
 * surface-driven `loading`-attribute selection.
 */
final class ImagePresentationBuilder {

	/**
	 * Build a §10.4 {@see ImageSet} from the supplied snapshot
	 * and render context.
	 *
	 * @param ProductSnapshot $snapshot Domain product snapshot.
	 * @param RenderContext   $context  Active render context.
	 */
	public function build( ProductSnapshot $snapshot, RenderContext $context ): ImageSet {
		$primaryUrl = $snapshot->primaryImage();
		if ( $primaryUrl === null || ! self::isHttpUrl( $primaryUrl ) ) {
			return new ImageSet( null, array() );
		}

		$loading = $this->resolveLoading( $context );
		$title   = $snapshot->title();

		$primary = new ImageView(
			$primaryUrl,
			$title,
			null,
			null,
			$loading
		);

		$gallery = array();
		$seen    = array( $primaryUrl => true );
		foreach ( $snapshot->galleryImages() as $url ) {
			if ( ! self::isHttpUrl( $url ) ) {
				continue;
			}

			if ( isset( $seen[ $url ] ) ) {
				continue;
			}

			$seen[ $url ] = true;
			$gallery[]    = new ImageView(
				$url,
				$title,
				null,
				null,
				$loading
			);
		}

		return new ImageSet( $primary, $gallery );
	}

	/**
	 * Resolve the §12.2 `loading` attribute per the surface-driven
	 * selection documented at the file level.
	 *
	 * @param RenderContext $context Active render context.
	 */
	private function resolveLoading( RenderContext $context ): string {
		if ( $context->isAmp() ) {
			return ImageView::LOADING_EAGER;
		}

		if ( $context->isEditor() ) {
			return ImageView::LOADING_EAGER;
		}

		if ( $context->lazyLoadEnabled() ) {
			return ImageView::LOADING_LAZY;
		}

		return ImageView::LOADING_EAGER;
	}

	/**
	 * Test whether a URL string starts with `http://` or
	 * `https://`.
	 *
	 * @param string $url Raw URL string.
	 */
	private static function isHttpUrl( string $url ): bool {
		$trimmed = trim( $url );
		if ( $trimmed === '' ) {
			return false;
		}

		return strpos( $trimmed, 'http://' ) === 0
			|| strpos( $trimmed, 'https://' ) === 0;
	}
}
