<?php

declare(strict_types=1);

namespace App\Domain\Media;

/**
 * Erzeugt HTML-Source-Sets für responsive Bilder
 */
final readonly class ImageSourceSetGenerator
{
    /**
     * Generiert ein vollständiges Picture-Element mit Source-Tags für verschiedene Formate und Größen
     *
     * @param Image $image Das Originalbild mit seinen Varianten
     * @param string $variantType Der Variantentyp (thumbnail, gallery, hero)
     * @param array $attributes Zusätzliche Attribute für das img-Tag (alt, class, etc.)
     * @return string HTML Picture-Element
     */
    public function generatePictureElement(Image $image, string $variantType = 'gallery', array $attributes = []): string
    {
        $sources = [];

        // Varianten nach Format gruppieren
        $variantsByFormat = $this->groupVariantsByFormat($image, $variantType);

        // Source-Tags für jedes Format generieren
        foreach (['avif', 'webp', 'jpeg'] as $format) {
            if (isset($variantsByFormat[$format])) {
                $sources[] = $this->generateSourceElement($variantsByFormat[$format], ImageFormat::from($format));
            }
        }

        // Fallback img-Tag
        $fallbackImage = $this->getFallbackImage($variantsByFormat, $image);
        $attributes['src'] = $fallbackImage->getUrl();
        $attributes['width'] = $fallbackImage->width;
        $attributes['height'] = $fallbackImage->height;

        if (! isset($attributes['alt'])) {
            $attributes['alt'] = '';
        }

        $sources[] = $this->generateImgTag($attributes);

        return '<picture>' . implode('', $sources) . '</picture>';
    }

    /**
     * Generiert einen einfachen source-Tag mit srcset
     *
     * @param array $variants Bildvarianten für ein bestimmtes Format
     * @param ImageFormat $format Das Bildformat
     * @return string HTML source-Element
     */
    private function generateSourceElement(array $variants, ImageFormat $format): string
    {
        $srcset = [];
        $sizes = [];

        // Nach Größe sortieren (klein nach groß)
        usort($variants, function (ImageVariant $a, ImageVariant $b) {
            return $a->width <=> $b->width;
        });

        foreach ($variants as $variant) {
            // URL und Breite für srcset
            $srcset[] = $variant->getUrl() . ' ' . $variant->width . 'w';

            // Sizes basierend auf Breakpoints
            $size = ImageSize::from($variant->size);
            $breakpoint = $size->getBreakpoint();

            if ($breakpoint !== null) {
                $sizes[] = "(max-width: {$breakpoint}px) {$variant->width}px";
            }
        }

        // Größte Variante als Standardgröße hinzufügen
        $sizes[] = $variants[count($variants) - 1]->width . 'px';

        return sprintf(
            '<source type="%s" srcset="%s" sizes="%s">',
            $format->getMimeType(),
            implode(', ', $srcset),
            implode(', ', $sizes)
        );
    }

    /**
     * Erzeugt einen img-Tag mit den angegebenen Attributen
     *
     * @param array $attributes HTML-Attribute für das img-Tag
     * @return string HTML img-Element
     */
    private function generateImgTag(array $attributes): string
    {
        $htmlAttributes = [];

        foreach ($attributes as $name => $value) {
            $htmlAttributes[] = sprintf('%s="%s"', $name, htmlspecialchars((string)$value));
        }

        return '<img ' . implode(' ', $htmlAttributes) . '>';
    }

    /**
     * Gruppiert Bildvarianten nach Format
     *
     * @param Image $image Das Originalbild
     * @param string $variantType Der Variantentyp
     * @return array Varianten gruppiert nach Format
     */
    private function groupVariantsByFormat(Image $image, string $variantType): array
    {
        $variantsByFormat = [];

        // Check if variants are loaded to avoid uninitialized readonly property error
        if (! isset($image->variants)) {
            return [];
        }

        foreach ($image->variants as $variant) {
            if ($variant->variantType === $variantType) {
                $variantsByFormat[$variant->format][] = $variant;
            }
        }

        return $variantsByFormat;
    }

    /**
     * Wählt das beste Fallback-Bild aus
     *
     * @param array $variantsByFormat Varianten gruppiert nach Format
     * @param Image $image Das Originalbild
     * @return ImageVariant Die beste Fallback-Variante
     */
    private function getFallbackImage(array $variantsByFormat, Image $image): ImageVariant
    {
        // Bevorzugen JPEG als Fallback
        if (! empty($variantsByFormat['jpeg'])) {
            // Mittlere Größe als Fallback verwenden
            $variants = $variantsByFormat['jpeg'];

            return $variants[min(1, count($variants) - 1)];
        }

        // Alternativen, falls kein JPEG verfügbar
        foreach (['webp', 'avif'] as $format) {
            if (! empty($variantsByFormat[$format])) {
                return $variantsByFormat[$format][0];
            }
        }

        // Wenn keine Varianten gefunden wurden, das Originalbild verwenden
        return new ImageVariant(
            imageId: (string) $image->ulid,
            variantType: 'original',
            size: 'original',
            format: pathinfo($image->filename, PATHINFO_EXTENSION),
            mimeType: $image->mimeType,
            fileSize: $image->fileSize,
            width: $image->width,
            height: $image->height,
            filename: $image->filename,
            path: $image->path->toString(),
        );
    }
}
