<?php declare(strict_types=1);

namespace Newland\NeosFiltering\Items;

use Doctrine\ORM\Query\Expr;
use Neos\Flow\Annotations as Flow;
use Newland\Contracts\Neos\Filter\CompositeFilterItem;
use Newland\Contracts\Neos\Filter\Expression;
use Newland\Contracts\Neos\Filter\FilterItem;
use Newland\NeosFiltering\Service\ExpressionService;
use Newland\NeosFiltering\Traits\FilterItemCommon;
use Newland\NeosFiltering\Traits\HasCombinationSettings;
use Newland\NeosFiltering\Traits\HasVisibleState;
use TYPO3Fluid\Fluid\Core\Rendering\RenderingContextInterface;

/**
 * Group of filter items that are combined with AND or OR logic.
 * This item has no rendered output itself: It simply renders it's children
 * and combines their query parts.
 */
class Group implements FilterItem, CompositeFilterItem
{
    use FilterItemCommon,
        HasCombinationSettings,
        HasVisibleState;

    /** @var FilterItem[] */
    protected $items = [];

    /**
     * @var ExpressionService
     * @Flow\Inject
     */
    protected $expressionService;

    public function __construct(array $items = [])
    {
        foreach ($items as $item) {
            $this->addItem($item);
        }
    }

    public function __clone()
    {
        $this->items = array_map(
            function (FilterItem $item) {
                return clone $item;
            },
            $this->items
        );
    }

    public function addItem(FilterItem $item): void
    {
        $this->items[] = $item;
    }

    public function getChildrenItems(): array
    {
        return $this->items;
    }

    public function queryExpression(Expr $expr): Expression
    {
        $expressions = array_map(
            function (FilterItem $item) use ($expr) {
                return $item->queryExpression($expr);
            },
            $this->items
        );
        $result = new Expression();
        $result->where = $this->combineWithCurrentCombinationMethod(
            $expr,
            $this->expressionService->extractWhere($expressions)
        );
        $result->having = $this->combineWithCurrentCombinationMethod(
            $expr,
            $this->expressionService->extractHaving($expressions)
        );
        return $result;
    }

    public function render(RenderingContextInterface $renderingContext)
    {
        return $this->renderItems($renderingContext);
    }

    protected function renderItems(RenderingContextInterface $renderingContext): string
    {
        $rendered = array_map(
            function (FilterItem $item) use ($renderingContext) {
                return $item->render($renderingContext);
            },
            $this->items
        );

        return trim(implode('', $rendered));
    }

    public function __toString(): string
    {
        $items = array_map(
            function (FilterItem $item) {
                return sprintf("\t%s\n", (string) $item);
            },
            (array) $this->items
        );
        $itemLines = explode("\n", implode("\n", $items));
        return sprintf("%s {\n\t%s\n}", static::class, implode("\n\t", $itemLines));
    }
}
