<?php declare(strict_types=1);

namespace Newland\Toubiz\Poi\Neos\Filter\Items;

use Doctrine\ORM\Query\Expr;
use Doctrine\ORM\QueryBuilder;
use Newland\Contracts\Neos\Filter\Expression;
use Newland\NeosFiltering\Items\Range;
use Newland\Toubiz\Sync\Neos\Domain\Model\Attribute;

class AttributeRange extends Range
{
    use AttributeCommon;

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

        $subQuery = $this->buildSubQuery();
        if ($subQuery === null) {
            return Expression::empty();
        }

        return Expression::where($expr->in($this->databaseColumn, $subQuery->getDQL()));
    }

    private function buildSubQuery(): ?QueryBuilder
    {
        $min = $this->state['min'] ?? null;
        $max = $this->state['max'] ?? null;

        if ($min === null || $max === null) {
            return null;
        }

        $alias = uniqid('attribute', false);
        $query = $this->initializeSubQuery($alias);
        $parts = $this->getBaseQueryParts($query->expr(), $alias);

        $parts[] = $query->expr()->gte(sprintf('CAST(%s.data AS decimal)', $alias), $min);
        $parts[] = $query->expr()->lte(sprintf('CAST(%s.data AS decimal)', $alias), $max);

        return $query->where($query->expr()->andX(...$parts));
    }

    protected function getBaseQueryParts(Expr $expr, string $alias): array
    {
        $parts = [];
        $name = $this->configuration['attributeName'];
        $type = $this->configuration['attributeType'] ?? null;

        // Note: There is a index on attribute for [ name, type ]. That's why we
        // add these WHERE statements first - to help mysql use that index.
        $parts[] = $expr->eq(sprintf('%s.name', $alias), $expr->literal($name));
        if ($type !== null) {
            $parts[] = $expr->eq(sprintf('%s.type', $alias), $expr->literal(Attribute::TYPE_NUMBER));
        }
        return $parts;
    }
}
