<?php
namespace Newland\Toubiz\Poi\Neos\Filter\Items;

/*
 * 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 Doctrine\ORM\Query\Expr;
use Doctrine\ORM\QueryBuilder;
use Newland\NeosFiltering\Contract\Expression;
use Newland\NeosFiltering\Contract\FilterItem;
use Newland\NeosFiltering\Contract\FilterItemCommon;
use Newland\NeosFiltering\Contract\HasCombinationSettings;
use Newland\NeosFiltering\Contract\HasInvisibleState;
use Newland\NeosFiltering\Contract\HasQueryString;
use Newland\NeosFiltering\Contract\NeedsDatabaseColumn;
use Newland\NeosFiltering\Contract\QueryBoundFilterItem;
use Newland\Toubiz\Sync\Neos\Domain\Model\Attribute;
use TYPO3Fluid\Fluid\Core\Rendering\RenderingContextInterface;

class PreselectedTags implements FilterItem, QueryBoundFilterItem
{
    use FilterItemCommon,
        AttributeCommon,
        NeedsDatabaseColumn,
        HasCombinationSettings,
        HasQueryString,
        HasInvisibleState;

    /**
     * Returns a database expression that mirrors the item state.
     * This expression is applied to a database query in order to get the filtered results.
     *
     * Note: The entity that is being filtered for is always aliased as `entity`.
     *
     * @param Expr $expr
     * @return Expression An expression built by the given expression builder.
     */
    public function queryExpression(Expr $expr): Expression
    {
        if (empty($this->state)) {
            return Expression::empty();
        }
        return Expression::where($expr->in($this->databaseColumn, $this->buildSubQuery()->getDQL()));
    }

    private function buildSubQuery(): QueryBuilder
    {
        $alias = uniqid('attribute', false);
        $query = $this->initializeSubQuery($alias);

        $parts = $this->getPartsForSubquery($query->expr(), $alias);
        $query->andWhere($query->expr()->orX(...$parts));

        if ($this->combine === static::$AND && \count($this->state) > 1) {
            $query->groupBy(sprintf('%s.article', $alias));
            $query->having($query->expr()->eq($query->expr()->countDistinct($alias), \count($this->state)));
        }

        return $query;
    }

    private function getPartsForSubquery(Expr $expr, string $alias): array
    {
        $parts = [];

        foreach ($this->getValuesForQueryFromCurrentState() as $attributeName => $typesAndValues) {
            foreach ($typesAndValues as $attributeType => $values) {
                $parts[] = $expr->andX(
                    $expr->eq(sprintf('%s.name', $alias), $expr->literal($attributeName)),
                    $expr->eq(sprintf('%s.type', $alias), $expr->literal($attributeType)),
                    $expr->in(sprintf('%s.data', $alias), $values)
                );
            }
        }

        return $parts;
    }

    /**
     * Returns a multi-dimensional array of the following format:
     * - First layer uses attribute names as keys
     * - Second layer uses attribute types as keys
     * - Leafs are arrays of attribute values.
     *
     * @return array
     */
    private function getValuesForQueryFromCurrentState(): array
    {
        $values = [];

        foreach ($this->state as $state) {
            if (strpos($state, ':') === false) {
                continue;
            }

            [ $attributeName, $attributeValue ] = explode(':', $state, 2);
            $attributeType = Attribute::TYPE_STRING;
            if (!array_key_exists($attributeName, $values)) {
                $values[$attributeName] = [];
            }
            if (!array_key_exists($attributeType, $values[$attributeName])) {
                $values[$attributeName][$attributeType] = [];
            }
            $values[$attributeName][$attributeType][] = $attributeValue;
        }

        return $values;
    }

    public function render(RenderingContextInterface $renderingContext)
    {
        $view = $this->initializeView(
            [ 'resource://Newland.Toubiz.Poi.Neos/Private/Templates/Filter/Items' ],
            [],
            [ 'resource://Newland.Toubiz.Poi.Neos/Private/Partials/Filter/Items' ]
        );

        $view->assignMultiple(
            [
                'stateValues' => $this->state,
            ]
        );

        return $view->render('TagList');
    }
}
