<?php declare(strict_types=1);

namespace Newland\NeosFiltering\Items;

use Doctrine\ORM\Query\Expr;
use Newland\Contracts\Neos\Filter\DataSourcedFilterItem;
use Newland\Contracts\Neos\Filter\Expression;
use Newland\Contracts\Neos\Filter\FilterItem;
use Newland\Contracts\Neos\Filter\ProvidesHiddenFieldsIfObstructed;
use Newland\Contracts\Neos\Filter\QueryBoundFilterItem;
use Newland\Contracts\Neos\Filter\StatusIndicatingFilterItem;
use Newland\Contracts\Neos\Filter\TitledFilterItem;
use Newland\NeosFiltering\StatusIndicator\StatusIndicator;
use Newland\NeosFiltering\Traits\FilterItemCommon;
use Newland\NeosFiltering\Traits\HasCombinationSettings;
use Newland\NeosFiltering\Traits\HasDataSource;
use Newland\NeosFiltering\Traits\HasQueryString;
use Newland\NeosFiltering\Traits\HasTitle;
use Newland\NeosFiltering\Traits\HasVisibleState;
use Newland\NeosFiltering\Traits\NeedsDatabaseColumn;
use TYPO3Fluid\Fluid\Core\Rendering\RenderingContextInterface;

/**
 * A collection of checkboxes that allow the user to select multiple values for the
 * same identifier.
 *
 * The state must be set to an array containing strings for all selected options.
 *
 * Additional to the usual data source attributes `label` and `value` a data source may also
 * specify `queryString`. If an option has a `queryString` that string will be used in the
 * query.
 *
 * @example
 * $field = new CheckboxCollection();
 * $field->setQueryString('tags');
 * $field->setTitle('Article Tags');
 * $field->setDatabaseColumn('entity.tag');
 * $field->setDataSource(new DirectDataSource([
 *      [ 'label' => 'Foo', value => 'foo' ],
 *      [ 'label' => 'Bar', 'value' => 'bar', 'queryString' => 'uebernachten' ],
 *      [ 'label' => 'Baz', 'value' => 'baz' ],
 * ]));
 * $field->setState([ 'foo', 'bar' ]);
 */
class CheckboxList implements
    FilterItem,
    QueryBoundFilterItem,
    DataSourcedFilterItem,
    TitledFilterItem,
    StatusIndicatingFilterItem,
    ProvidesHiddenFieldsIfObstructed
{
    use FilterItemCommon,
        HasTitle,
        HasVisibleState,
        NeedsDatabaseColumn,
        HasDataSource,
        HasCombinationSettings,
        HasQueryString {
        HasDataSource::setState insteadof FilterItemCommon;
    }

    public function queryExpression(Expr $expr): Expression
    {
        $this->throwIfNoDatabaseColumnDeclared();
        $this->throwIfNoQueryString();
        $state = $this->getStateValuesForQuery();

        if (\count($state) === 0) {
            return Expression::empty();
        }

        if (\count($state) === 1) {
            return Expression::where($expr->eq($this->databaseColumn, $expr->literal(array_values($state)[0])));
        }

        $expression = Expression::where($expr->in($this->databaseColumn, $state));
        if ($this->combine === static::$AND) {
            $expression->having = $expr->gte($expr->countDistinct($this->databaseColumn), \count($state));
        }
        return $expression;
    }

    public function render(RenderingContextInterface $renderingContext)
    {
        $view = $this->initializeView();
        $view->assignMultiple(
            [
                'options' => $this->getDataFromDataSource(),
                'stateValues' => $this->getStateValues(),
            ]
        );
        return $view->render('CheckboxList');
    }

    /** @return StatusIndicator[] */
    public function getStatusIndicators(): array
    {
        return array_map(
            function (array $item) {
                return StatusIndicator::fromOption($item);
            },
            (array) $this->state
        );
    }
}
