<?php declare(strict_types=1);

namespace Newland\NeosFiltering\Items;

use Doctrine\ORM\Query\Expr;
use Newland\Contracts\Neos\Filter\Expression;
use Newland\Contracts\Neos\Filter\FilterItem;
use Newland\Contracts\Neos\Filter\FilterRoot;
use Newland\Contracts\Neos\Filter\ProvidesHiddenFields;
use Newland\Contracts\Neos\Filter\ProvidesHiddenFieldsIfObstructed;
use TYPO3Fluid\Fluid\Core\Parser\SyntaxTree\NodeInterface;
use TYPO3Fluid\Fluid\Core\Rendering\RenderingContextInterface;

/**
 * Filter item that obstructs some access to the contained item.
 * This filter item will pass through all calls to the obstructed items but
 * - It will not render anything
 * - Because it does not implement the `QueryBoundFilterItem` interface all query bindings of
 *   the obstructed item will be ignored.
 *
 * This item can be used to preserver the functionality of a filter item while removing it
 * from the visual output.
 */
class ObstructionWrapper implements FilterItem, ProvidesHiddenFields
{

    /** @var FilterItem */
    protected $obstructed;

    public function __construct(FilterItem $obstructed)
    {
        $this->obstructed = $obstructed;
    }

    /** @return mixed */
    public function __call(string $method, array $arguments)
    {
        return $this->obstructed->{$method}(...$arguments);
    }

    public function __clone()
    {
        $this->obstructed = clone $this->obstructed;
    }

    public function queryExpression(Expr $expr): Expression
    {
        return $this->obstructed->queryExpression($expr);
    }

    public function setState($value): void
    {
        $this->obstructed->setState($value);
    }

    public function getName()
    {
        return $this->obstructed->getName();
    }

    public function setName($name)
    {
        $this->obstructed->setName($name);
    }

    public function setNode(NodeInterface $node): void
    {
        $this->obstructed->setNode($node);
    }

    public function getNode()
    {
        return $this->obstructed->getNode();
    }

    public function render(RenderingContextInterface $renderingContext)
    {
        return null;
    }

    public function getId(): string
    {
        return $this->obstructed->getId();
    }

    public function setRoot(FilterRoot $root): void
    {
        $this->obstructed->setRoot($root);
    }

    public function getRoot(): FilterRoot
    {
        return $this->obstructed->getRoot();
    }

    public function setConfiguration(array $configuration): void
    {
        $this->obstructed->setConfiguration($configuration);
    }

    public function getObstructed(): FilterItem
    {
        return $this->obstructed;
    }

    public function getConfiguration(): array
    {
        return $this->obstructed->getConfiguration();
    }

    public function getState()
    {
        return $this->obstructed->getState();
    }

    public function hasVisibleState(): bool
    {
        return $this->obstructed->hasVisibleState();
    }

    /**
     * Fix for fluid using `getVisibleState` instead of `hasVisibleState` as all methods are callable
     * on this class (Because it has __call).
     */
    public function getVisibleState(): bool
    {
        return $this->obstructed->hasVisibleState();
    }

    public function __toString(): string
    {
        return sprintf('%s { %s }', __CLASS__, (string) $this->obstructed);
    }

    public function getHiddenFields(): array
    {
        $fields = [ [ ] ];
        if ($this->obstructed instanceof ProvidesHiddenFields) {
            $fields[] = $this->obstructed->getHiddenFields();
        }
        if ($this->obstructed instanceof ProvidesHiddenFieldsIfObstructed) {
            $fields[] = $this->obstructed->getHiddenFieldsIfObstructed();
        }
        return array_merge(...$fields);
    }
}
