<?php declare(strict_types=1);

namespace Newland\Toubiz\Common\Neos\ViewHelpers\Slot;

use Neos\ContentRepository\Domain\Model\NodeInterface;
use Neos\Flow\Annotations as Flow;
use Neos\Flow\Configuration\ConfigurationManager;
use Neos\Flow\Mvc\ActionRequest;
use Neos\Flow\Utility\Environment;
use Neos\FluidAdaptor\Core\ViewHelper\AbstractViewHelper;

/**
 * Renders a slot. The fusion object name that is being rendered will be automatically generated out of the package
 * of the calling template and the slot name passed in the format `{Package}:Slot.{SlotName}`.
 *
 * This means, when calling from the POI package using `<common:slot.render slot="foo" />` the fusion object
 * `Newland.Toubiz.Poi.Neos:Slot.Foo` will be rendered.
 */
class RenderViewHelper extends AbstractViewHelper
{
    protected $escapeOutput = false;

    /**
     * @var ConfigurationManager
     * @Flow\Inject()
     */
    protected $configurationManager;

    /**
     * @var Environment
     * @Flow\Inject()
     */
    protected $environment;

    /**
     * @var FusionRuntimeManager
     * @Flow\Inject()
     */
    protected $runtimeManager;

    public function render(string $slot, array $context = []): string
    {
        $context['contextKeys'] = array_keys($context);
        $context['slot'] = $slot;
        $context['prototype'] = $this->prototypeName($slot);
        $context['highlightSlots'] = $this->highlightSlots();
        $context['package'] = $this->request()->getControllerPackageKey();
        $context['controller'] = $this->request()->getControllerName();
        $context['action'] = $this->request()->getControllerActionName();

        // Fusion paths refer to uninitialized prototypes with <pointy-brackets>
        $rendered = $this->renderFusion(sprintf('<%s>', $context['prototype']), $context);

        if (!$this->environment->getContext()->isProduction()) {
            return sprintf(
                '<!-- %s --> %s',
                $this->describingComment($slot, $context['prototype'], $context['contextKeys']),
                $rendered
            );
        }

        return $rendered;
    }

    private function describingComment(string $slot, string $prototype, array $contextKeys): string
    {
        return sprintf(
            'Slot(%s): Implement prototype(%s) to display. The following keys are available in fusion: [%s]',
            $slot,
            $prototype,
            implode(', ', $contextKeys)
        );
    }

    private function prototypeName(string $slot): string
    {
        $request = $this->request();
        return sprintf(
            '%s:Slot.%s',
            $request->getControllerPackageKey(),
            ucfirst($slot)
        );
    }

    private function renderFusion(string $path, array $context): string
    {
        /** @var NodeInterface|null $node */
        $node = $this->request()->getInternalArgument('__node');
        if (!$node) {
            return '';
        }


        $runtime = $this->runtimeManager->runtime($node);
        $runtime->pushApplyValues($context);
        $runtime->pushContextArray($context);
        $rendered = $runtime->render($path);
        $runtime->popContext();
        return $rendered ?? '';
    }

    private function request(): ActionRequest
    {
        $request = $this->controllerContext->getRequest();
        if (!($request instanceof ActionRequest)) {
            throw new \InvalidArgumentException('ViewHelper is expected to be called in action request');
        }

        return $request;
    }

    private function highlightSlots(): bool
    {
        $settingsPath = sprintf('%s.debug.highlightSlots', $this->request()->getControllerPackageKey());
        $highlight = $this->configurationManager->getConfiguration(
            ConfigurationManager::CONFIGURATION_TYPE_SETTINGS,
            $settingsPath
        ) ?? false;
        return (bool) $highlight;
    }
}
