<?php

/*
 * This file is part of the "news" Extension for TYPO3 CMS.
 *
 * For the full copyright and license information, please read the
 * LICENSE.txt file that was distributed with this source code.
 */

namespace GeorgRinger\News\ViewHelpers;

use GeorgRinger\News\Domain\Model\FileReference;
use GeorgRinger\News\Domain\Model\News;
use TYPO3\CMS\Core\Imaging\ImageManipulation\CropVariantCollection;
use TYPO3\CMS\Core\Resource\File;
use TYPO3\CMS\Core\Resource\FileInterface;
use TYPO3\CMS\Core\Resource\Rendering\RendererRegistry;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Extbase\Service\ImageService;
use TYPO3Fluid\Fluid\Core\ViewHelper\AbstractViewHelper;

class RenderMediaViewHelper extends AbstractViewHelper
{
    protected $escapeOutput = false;

    protected $escapingInterceptorEnabled = false;

    /** @var string */
    protected $mediaTag = '/\\[media\\]/';

    protected $replaceMediaTag = '/(?:<p>\s*)?\\[media\\](?:\s*<\/p>)?/';

    protected string $imgClass = '';
    protected string $videoClass = '';
    protected string $audioClass = '';

    /**
     * Initialize all arguments. You need to override this method and call
     * $this->registerArgument(...) inside this method, to register all your arguments.
     *
     * @api
     */
    public function initializeArguments(): void
    {
        $this->registerArgument('news', 'object', 'the news post', true);
        $this->registerArgument('imgClass', 'string', 'add css class to images', false, '');
        $this->registerArgument('videoClass', 'string', 'wrap videos in a div with this class', false, '');
        $this->registerArgument('audioClass', 'string', 'wrap audio files in a div with this class', false, '');
        $this->registerArgument('fileIndex', 'int', 'index of image to start with', false, 0);
        $this->registerArgument('cropVariant', 'string', 'select a cropping variant, in case multiple croppings have been specified or stored in FileReference', false, 'default');
    }

    private function renderImage(FileInterface $image): string
    {
        $imageService = GeneralUtility::makeInstance(ImageService::class);

        $cropString = '';
        if ($image->hasProperty('crop')) {
            $cropString = $image->getProperty('crop');
        }
        $cropVariantCollection = CropVariantCollection::create((string)$cropString);
        $cropVariant = $this->arguments['cropVariant'] ?: 'default';
        $cropArea = $cropVariantCollection->getCropArea($cropVariant);
        $processingInstructions = [
            'width' => null,
            'height' => null,
            'crop' => $cropArea->isEmpty() ? null : $cropArea->makeAbsoluteBasedOnFile($image),
        ];

        $processedImage = $imageService->applyProcessingInstructions($image, $processingInstructions);
        $imageUri = $imageService->getImageUri($processedImage);

        $alt = trim((string)$image->getProperty('alternative'));
        $title = trim((string)$image->getProperty('title'));
        $description = trim((string)$image->getProperty('description'));

        $imageAttributes = [
            'src' => $imageUri,
            'alt' => $alt ?: ($title ?: ''),
            'title' => $title,
        ];

        if (!empty($this->imgClass)) {
            $imageAttributes['class'] = $this->imgClass;
        }

        $imageAttributes = array_reduce(
            array_keys($imageAttributes),
            static fn(string $carry, string $key): string => $carry . ' ' . $key . '="' . htmlspecialchars($imageAttributes[$key]) . '"',
            ''
        );

        if (!empty($description)) {
            $description = '<figcaption>' . htmlspecialchars($description) . '</figcaption>';
        }

        return '<figure><img ' . $imageAttributes . ' />' . $description . '</figure>';
    }

    /**
     * Replace the [media] tags with the output of the according media render output
     *
     * @param string $content
     */
    private function renderMedia($content, array $files): string
    {
        $fileIndex = $this->arguments['fileIndex'];
        preg_match_all($this->mediaTag, $content, $matches);
        foreach ($matches[0] as $_) {
            /** @var FileReference $file */
            $file = null;
            /** @var \TYPO3\CMS\Core\Resource\FileReference $media */
            $media = null;

            // check if a file is present for current media tag
            if (count($files) <= $fileIndex) {
                break;
            }

            $file = $files[$fileIndex++];
            if ($file === null) {
                break;
            }

            $media = $file->getOriginalResource();
            $fileRenderer = GeneralUtility::makeInstance(RendererRegistry::class)->getRenderer($media);

            // if a renderer is configured for the file type use this renderer
            if ($fileRenderer !== null) {
                $media_tag = $fileRenderer->render($media, 0, 0);

                // check if media tag needs to be wrapped in div, depends on type of media file
                $wrapClass = '';
                if ($media->getType() === File::FILETYPE_VIDEO) {
                    $wrapClass = $this->videoClass;
                } elseif ($media->getType() === File::FILETYPE_AUDIO) {
                    $wrapClass = $this->audioClass;
                }

                if (!empty($wrapClass)) {
                    $media_tag = '<div class="' . $wrapClass . '">' . $media_tag . '</div>';
                }
            }
            // fallback to image rendering
            else {
                $media_tag = $this->renderImage($media);
            }

            // replace one tag in content with render output
            $content = preg_replace($this->replaceMediaTag, $media_tag, $content, 1);
        }

        return $content;
    }

    public function render(): string
    {
        /** @var News $news */
        $news = $this->arguments['news'];

        $this->imgClass = htmlspecialchars((string)($this->arguments['imgClass'] ?? ''));
        $this->videoClass = htmlspecialchars((string)($this->arguments['videoClass'] ?? ''));
        $this->audioClass = htmlspecialchars((string)($this->arguments['audioClass'] ?? ''));

        $mediaFiles = $news->getMediaNonPreviews();

        $content = $this->renderChildren();
        return $this->renderMedia($content, $mediaFiles);
    }
}
