<?php declare(strict_types=1);

namespace Newland\Toubiz\Sync\Neos\Translation;

use Neos\ContentRepository\Domain\Model\NodeInterface;
use Neos\Flow\Aop\JoinPointInterface;
use Neos\Flow\I18n;
use Neos\Flow\Annotations as Flow;

/**
 * Service that fetches the current *content* language of a page load from different sources.
 * Note: This service does not use a conventional getter since the time when it is injected (initialization time)
 *       and the time when a language is available (runtime) may be different. Instead, a function is called when
 *       new information is available. Since all the sources of languages are evaluated very early the timing should
 *       not make a difference for classes using the service.
 *
 * @Flow\Scope("singleton")
 * @Flow\Aspect()
 */
class CurrentLanguageService
{

    /**
     * @var callable[]
     */
    protected $listeners = [];

    /** @var string|null */
    protected $language;

    public function updateLanguage(string $language): void
    {
        $this->language = $language;
        foreach ($this->listeners as $block) {
            $block($language);
        }
    }

    public function onInitialLanguageAvailable(callable $block): void
    {
        $this->listeners[] = $block;
        $block($this->language);
    }

    /**
     * Uses the current interface language if nothing else has been specified.
     */
    public function injectLanguage(I18n\Service $localizationService): void
    {
        if ($this->language === null) {
            $this->updateLanguage($localizationService->getConfiguration()->getCurrentLocale()->getLanguage());
        }
    }

    /**
     * Uses language of the currently displayed node if in a frontend context.
     *
     * @Flow\Before("method(Neos\Neos\Controller\Frontend\NodeController->showAction())")
     */
    public function setLanguageForFrontendAccess(JoinPointInterface $joinPoint): void
    {
        $this->updateLanguageFromNode($joinPoint->getMethodArguments()['node'] ?? null);
    }

    /**
     * Uses language of the currently displayed node if in a frontend context.
     *
     * @Flow\Before("method(Newland\PageFrameProvider\Controller\PageFrameController->showAction())")
     */
    public function setLanguageForPageFrame(JoinPointInterface $joinPoint): void
    {
        $this->updateLanguageFromNode($joinPoint->getMethodArguments()['node'] ?? null);
    }

    /**
     * Uses the language of the node selected in the backend as content language when the request
     * is for a data source. This ensures that data sources return contents in the correct language
     * for a page.
     *
     * @Flow\Before("method(Neos\Neos\Service\Controller\DataSourceController->indexAction())")
     */
    public function setLanguageForDataSource(JoinPointInterface $joinPoint): void
    {
        $this->updateLanguageFromNode($joinPoint->getMethodArguments()['node'] ?? null);
    }

    /**
     * This method only exists to ensure a clean state when testing and should never be used in
     * runtime code.
     * @internal
     */
    public function reset(): void
    {
        $this->language = null;
    }

    /**
     * @param NodeInterface|mixed $node
     */
    public function updateLanguageFromNode($node): void
    {
        if (!($node instanceof NodeInterface)) {
            return;
        }

        $language = $node->getDimensions()['language'][0] ?? null;
        if ($language !== null) {
            $this->updateLanguage((string) $language);
        }
    }
}
