<?php
namespace Newland\Toubiz\Poi\Neos\Controller;

/*
 * This file is part of the "toubiz-poi-neos" package.
 *
 * For the full copyright and license information, please read the
 * LICENSE.txt file that was distributed with this source code.
 */

use Neos\ContentRepository\Domain\Model\Node;
use Neos\Flow\Annotations as Flow;
use Neos\Flow\Configuration\ConfigurationManager;
use Neos\Flow\I18n\Locale;
use Neos\Flow\I18n\Service;
use Neos\FluidAdaptor\View\StandaloneView;
use Neos\Flow\Mvc\Controller\ActionController;
use Neos\Flow\Mvc\View\ViewInterface;
use Neos\Neos\Domain\Model\Site;
use Neos\Neos\Domain\Service\ContentContext;
use Newland\NeosCommon\Service\NodeService;
use Newland\Toubiz\Poi\Neos\Service\RegionService;
use Newland\Toubiz\Poi\Neos\Utility\ArrayUtility;
use Newland\Toubiz\Sync\Neos\Domain\Filter\ArticleFilter;
use Newland\Toubiz\Sync\Neos\Domain\Model\ZipCode;
use Newland\Toubiz\Sync\Neos\Domain\Repository\ArticleRepository;

abstract class AbstractActionController extends ActionController
{

    /**
     * The configuration for this package.
     *
     * @Flow\InjectConfiguration
     * @var array
     */
    protected $configuration;

    /**
     * @var Node The current node.
     */
    protected $node;

    /**
     * @var Site
     */
    protected $currentSite;

    /**
     * @var array
     */
    protected $properties;

    /**
     * @var array
     */
    protected $filterSections;

    /**
     * @var array
     * @Flow\InjectConfiguration("filter.sections")
     */
    protected $filterSectionConfiguration;

    /**
     * @var array
     * @Flow\InjectConfiguration("filter.options")
     */
    protected $filterOptions;

    /**
     * @Flow\Inject()
     * @var Service
     */
    protected $i18nService;

    /**
     * @var RegionService
     * @Flow\Inject()
     */
    protected $regionService;

    /**
     * @var ArticleRepository
     * @Flow\Inject()
     */
    protected $articleRepository;

    /**
     * @var NodeService
     * @Flow\Inject()
     */
    protected $nodeService;

    protected function initializeAction(Node $node = null): void
    {
        $this->initializeNode($node);
    }

    /**
     * This may be called manually inside an action
     * that is working outside of the default node/context,
     * like an AJAX action.
     *
     * @param Node|null $node
     */
    protected function initializeNode(Node $node = null): void
    {
        if ($node !== null) {
            $this->node = $node;
            $this->setLocale();
        } else {
            $node = $this->currentNode();
        }

        if ($node === null) {
            return;
        }

        $this->node = $node;

        $this->properties = (array) $this->node->getProperties();

        $context = $this->node->getContext();
        if ($context instanceof ContentContext) {
            $this->currentSite = $context->getCurrentSite();
        }

        $this->explicitlySetLanguageOfRepositoryBasedOnCurrentNode($this->node);
    }

    private function explicitlySetLanguageOfRepositoryBasedOnCurrentNode(Node $node): void
    {
        $language = $node->getDimensions()['language'][0] ?? null;
        if ($language === null) {
            return;
        }
        $this->articleRepository->setLanguage($language);
    }

    protected function buildFilterSections(): array
    {
        $articleType = $this->properties['articleType'] ?? null;
        $filterProperties = $this->filterSectionConfiguration[$articleType] ?? null;
        if (!$filterProperties || !array_key_exists('sections', $filterProperties)) {
            return [];
        }

        $filterSections = [];
        $filterOptions = $this->filterOptions;
        foreach ($filterProperties['sections'] as $index => $section) {
            $fieldSetOptions = [];
            foreach ($section['fieldSets'] as $fieldSet) {
                if (array_key_exists($fieldSet, $filterOptions)) {
                    $fieldSetOptions[$fieldSet] = $filterOptions[$fieldSet];
                }
            }
            $section['fieldSets'] = $fieldSetOptions;
            $filterSections[$index] = $section;
        }

        return $filterSections;
    }

    protected function initializeFilter(array $query): ArticleFilter
    {
        $articleFilter = new ArticleFilter;
        $articleFilter->setLimit($this->configuration['gridItemsPerPage']);

        if (array_key_exists('articleType', $query)) {
            $articleFilter->setMainType($query['articleType']);
        }

        if (array_key_exists('categories', $query) && is_array($query['categories'])) {
            $articleFilter->setCategories($query['categories']);
        }

        if (array_key_exists('attributes', $query)) {
            // Parameters coming from view are in the format of [property => [ in => [ value => value ] ] ]
            // Filter assumes [property => [ value, value, value ]]
            $articleFilter->setAttributesIn(
                array_map(
                    function (array $in) {
                        return array_values($in);
                    },
                    array_column($query['attributes'], 'in')
                )
            );
            $articleFilter->setAttributesRange(
                array_filter(
                    $query['attributes'],
                    function (array $config) {
                        return ($config['min'] ?? '') !== ''
                            && ($config['max'] ?? '') !== '';
                    }
                )
            );
        }

        if (array_key_exists('regions', $query)) {
            $articleFilter->setZips($this->regionService->getZipsFromRegionKeys($query['regions']));
        }

        if (array_key_exists('city', $query)) {
            $city = $this->articleRepository->findByIdentifier($query['city']);
            if ($city && $city->getCityData() !== null) {
                $zipCodes = array_map(
                    function (ZipCode $zipCode) {
                        return $zipCode->getZipCode();
                    },
                    $city->getCityData()->getZipCodes()->toArray()
                );
                $articleFilter->setZips($zipCodes);
            }
        }


        return $articleFilter;
    }

    /**
     * View initializer.
     *
     * Called before a view is rendered.
     *
     * @param ViewInterface $view
     * @return void
     */
    protected function initializeView(ViewInterface $view): void
    {
        $view->assignMultiple(
            [
                'configuration' => $this->configuration,
                'node' => $this->node,
            ]
        );
    }

    /**
     * Creates a fluid standalone view.
     *
     * @param string $template Path to the template to render.
     * @return StandaloneView
     */
    protected function createStandaloneView(string $template): StandaloneView
    {
        $view = new StandaloneView;
        $this->initializeView($view);
        $view->setTemplateRootPaths([ 'resource://Newland.Toubiz.Poi.Neos/Private/Templates' ]);
        $view->setPartialRootPaths([ 'resource://Newland.Toubiz.Poi.Neos/Private/Partials' ]);
        $view->setTemplate($template);

        $dimensions = $this->node->getContext()->getDimensions();
        if (array_key_exists('language', $dimensions) && $dimensions['language'] !== []) {
            $currentLocale = new Locale($dimensions['language'][0]);
            $configuration = $this->i18nService->getConfiguration();
            $configuration->setCurrentLocale($currentLocale);
            $configuration->setFallbackRule(
                [
                    'strict' => false,
                    'order' => array_reverse($dimensions['language']),
                ]
            );
        }

        return $view;
    }

    /**
     * Forces the locale to the Node
     * Required for AJAX requests with translations
     */
    protected function setLocale(): void
    {
        $dimensions = $this->node->getContext()->getDimensions();
        if (array_key_exists('language', $dimensions) && $dimensions['language'] !== []) {
            $currentLocale = new Locale($dimensions['language'][0]);
            $configuration = $this->i18nService->getConfiguration();
            $configuration->setCurrentLocale($currentLocale);
            $configuration->setFallbackRule(
                [
                    'strict' => false,
                    'order' => array_reverse($dimensions['language']),
                ]
            );
        }
    }

    private function currentNode(): ?Node
    {
        /** @var Node|null $node */
        $node = $this->request->getInternalArgument('__node');

        return ($node instanceof Node) ? $node : null;
    }
}
