<?php

declare(strict_types=1);

/*
 * Chill is a software for social workers
 *
 * For the full copyright and license information, please view
 * the LICENSE file that was distributed with this source code.
 */

namespace Chill\MainBundle\Search;

/**
 * This create a query optimized for searching for the api response.
 *
 * When build, this class generate a SQL string and a list of a parameters which is suitable for running
 * a native SQL query. This have usually the form of
 *
 * `SELECT '<key>' as key, <metadata> as metadata, <pertinence> as pertinence FROM <from clause> WHERE <where clause>`.
 *
 * The clause between `<>` are provided through the dedicated method in this class (@see{self::setSelectKey},
 *
 * @see{self::setFromClause}), etc.).
 */
class SearchApiQuery
{
    private ?string $fromClause = null;

    private array $fromClauseParams = [];

    private bool $isDistinct = false;

    private ?string $isDistinctKey = null;

    private ?string $jsonbMetadata = null;

    private array $jsonbMetadataParams = [];

    private ?string $pertinence = null;

    private array $pertinenceParams = [];

    private array $select = [];

    private ?string $selectKey = null;

    private array $selectKeyParams = [];

    private array $selectParams = [];

    private array $whereClauses = [];

    private array $whereClausesParams = [];

    public function addSelectClause(string $select, array $params = []): self
    {
        $this->select[] = $select;
        $this->selectParams = [...$this->selectParams, ...$params];

        return $this;
    }

    /**
     * Add a where clause.
     *
     * This will add to previous where clauses with and `AND` join
     *
     * @return $this
     */
    public function andWhereClause(string $whereClause, array $params = []): self
    {
        $this->whereClauses[] = $whereClause;
        \array_push($this->whereClausesParams, ...$params);

        return $this;
    }

    public function buildParameters(bool $countOnly = false): array
    {
        if (!$countOnly) {
            return [
                ...$this->buildSelectParams($countOnly),
                ...$this->fromClauseParams,
                ...$this->whereClausesParams,
            ];
        }

        return [
            ...$this->fromClauseParams,
            ...$this->whereClausesParams,
        ];
    }

    public function buildQuery(bool $countOnly = false): string
    {
        $isMultiple = \count($this->whereClauses);
        $where =
            ($isMultiple ? '(' : '').
            \implode(
                ($isMultiple ? ')' : '').' AND '.($isMultiple ? '(' : ''),
                $this->whereClauses
            ).
            ($isMultiple ? ')' : '');

        $select = $this->buildSelectClause($countOnly);

        return \strtr('SELECT
                {select}
            FROM {from}
            WHERE {where}
        ', [
            '{select}' => $select,
            '{from}' => $this->fromClause,
            '{where}' => $where,
        ]);
    }

    public function getDistinct(): bool
    {
        return $this->isDistinct;
    }

    public function getFromClause(): string
    {
        return $this->fromClause;
    }

    public function getFromParams(): array
    {
        return $this->fromClauseParams;
    }

    public function getSelectClauses(): array
    {
        return $this->select;
    }

    public function getSelectParams(): array
    {
        return $this->selectParams;
    }

    public function resetSelectClause(): self
    {
        $this->select = [];
        $this->selectParams = [];
        $this->selectKey = null;
        $this->selectKeyParams = [];
        $this->jsonbMetadata = null;
        $this->jsonbMetadataParams = [];
        $this->pertinence = null;
        $this->pertinenceParams = [];

        return $this;
    }

    public function setDistinct(bool $distinct, string $distinctKey): self
    {
        $this->isDistinct = $distinct;
        $this->isDistinctKey = $distinctKey;

        return $this;
    }

    public function setFromClause(string $fromClause, array $params = []): self
    {
        $this->fromClause = $fromClause;
        $this->fromClauseParams = $params;

        return $this;
    }

    public function setSelectJsonbMetadata(string $jsonbMetadata, array $params = []): self
    {
        $this->jsonbMetadata = $jsonbMetadata;
        $this->jsonbMetadataParams = $params;

        return $this;
    }

    public function setSelectKey(string $selectKey, array $params = []): self
    {
        $this->selectKey = $selectKey;
        $this->selectKeyParams = $params;

        return $this;
    }

    public function setSelectPertinence(string $pertinence, array $params = []): self
    {
        $this->pertinence = $pertinence;
        $this->pertinenceParams = $params;

        return $this;
    }

    /**
     * Set the where clause and replace all existing ones.
     */
    public function setWhereClauses(string $whereClause, array $params = []): self
    {
        $this->whereClauses = [$whereClause];
        $this->whereClausesParams = $params;

        return $this;
    }

    private function buildSelectClause(bool $countOnly = false): string
    {
        if ($countOnly) {
            if (!$this->isDistinct) {
                return 'count(*) AS c';
            }

            return 'count(distinct '.$this->isDistinctKey.') AS c';
        }

        $selects = $this->getSelectClauses();

        if (null !== $this->selectKey) {
            $selects[] = \strtr("'{key}' AS key", ['{key}' => $this->selectKey]);
        }

        if (null !== $this->jsonbMetadata) {
            $selects[] = \strtr('{metadata} AS metadata', ['{metadata}' => $this->jsonbMetadata]);
        }

        if (null !== $this->pertinence) {
            $selects[] = \strtr('{pertinence} AS pertinence', ['{pertinence}' => $this->pertinence]);
        }

        return ($this->isDistinct ? 'DISTINCT ' : '').\implode(', ', $selects);
    }

    private function buildSelectParams(bool $count = false): array
    {
        if ($count) {
            return [];
        }

        $args = $this->getSelectParams();

        if (null !== $this->selectKey) {
            $args = [...$args, ...$this->selectKeyParams];
        }

        if (null !== $this->jsonbMetadata) {
            $args = [...$args, ...$this->jsonbMetadataParams];
        }

        if (null !== $this->pertinence) {
            $args = [...$args, ...$this->pertinenceParams];
        }

        return $args;
    }
}
