<?php
/**
 * Copyright since 2025 InPost S.A.
 *
 * For the full license information, please view the LICENSE file bundled with the module.
 *
 * @author InPost S.A.
 * @copyright since 2025 InPost S.A.
 * @license MIT
 */

declare(strict_types=1);

namespace InPost\International\PrestaShop\ObjectModel;

if (!defined('_PS_VERSION_')) {
    exit;
}

/**
 * @template T of \ObjectModel
 */
final class QueryBuilder
{
    /**
     * @var ObjectManagerInterface
     */
    private $manager;

    /**
     * @var class-string<T>
     */
    private $class;

    /**
     * @var int|null
     */
    private $languageId;

    /**
     * @var \DbQuery
     */
    private $qb;

    /**
     * @param class-string<T> $class
     */
    public function __construct(ObjectManagerInterface $manager, string $class, int $languageId = null)
    {
        $this->manager = $manager;
        $this->class = $class;
        $this->languageId = $languageId;
        $this->qb = new \DbQuery();
    }

    /**
     * @return $this
     */
    public function setLanguageId(?int $languageId): self
    {
        $this->languageId = $languageId;

        return $this;
    }

    /**
     * @return Query<T>
     */
    public function build(): Query
    {
        $sql = $this->qb->build();

        return new Query($this->manager, $this->class, $sql, $this->languageId);
    }

    /**
     * @return $this
     */
    public function select(string $select): self
    {
        $this->replaceQueryParts([
            'select' => [$select],
        ]);

        return $this;
    }

    /**
     * @return $this
     */
    public function addSelect(string $select): self
    {
        $this->qb->select($select);

        return $this;
    }

    /**
     * @return $this
     */
    public function from(string $table, string $alias = null): self
    {
        $this->qb->from($table, $alias);

        return $this;
    }

    /**
     * @return $this
     */
    public function innerJoin(string $table, string $alias = null, string $condition = null): self
    {
        $this->qb->innerJoin($table, $alias, $condition);

        return $this;
    }

    /**
     * @return $this
     */
    public function leftJoin(string $table, string $alias = null, string $condition = null): self
    {
        $this->qb->leftJoin($table, $alias, $condition);

        return $this;
    }

    /**
     * @return $this
     */
    public function rightJoin(string $table, string $alias = null, string $condition = null): self
    {
        $this->qb->rightJoin($table, $alias, $condition);

        return $this;
    }

    /**
     * @return $this
     */
    public function where(string $where): self
    {
        $this->replaceQueryParts([
            'where' => [$where],
        ]);

        return $this;
    }

    /**
     * @return $this
     */
    public function andWhere(string $where): self
    {
        $this->qb->where($where);

        return $this;
    }

    /**
     * @return $this
     */
    public function groupBy(string $groupBy): self
    {
        $this->replaceQueryParts([
            'group' => [$groupBy],
        ]);

        return $this;
    }

    /**
     * @return $this
     */
    public function addGroupBy(string $groupBy): self
    {
        $this->qb->groupBy($groupBy);

        return $this;
    }

    /**
     * @return $this
     */
    public function having(string $restriction): self
    {
        $this->replaceQueryParts([
            'having' => [$restriction],
        ]);

        return $this;
    }

    /**
     * @return $this
     */
    public function andHaving(string $restriction): self
    {
        $this->qb->having($restriction);

        return $this;
    }

    /**
     * @return $this
     */
    public function orderBy(string $orderBy): self
    {
        $this->replaceQueryParts([
            'order' => [$orderBy],
        ]);

        return $this;
    }

    /**
     * @return $this
     */
    public function addOrderBy(string $orderBy): self
    {
        $this->qb->orderBy($orderBy);

        return $this;
    }

    /**
     * @return $this
     */
    public function limit(int $limit, int $offset = 0): self
    {
        $this->qb->limit($limit, $offset);

        return $this;
    }

    /**
     * @return $this
     */
    public function resetWhere(): self
    {
        $this->replaceQueryParts([
            'where' => [],
        ]);

        return $this;
    }

    /**
     * @return $this
     */
    public function resetGroupBy(): self
    {
        $this->replaceQueryParts([
            'group' => [],
        ]);

        return $this;
    }

    /**
     * @return $this
     */
    public function resetHaving(): self
    {
        $this->replaceQueryParts([
            'having' => [],
        ]);

        return $this;
    }

    /**
     * @return $this
     */
    public function resetOrderBy(): self
    {
        $this->replaceQueryParts([
            'order' => [],
        ]);

        return $this;
    }

    /**
     * @param array<string, mixed[]> $parts
     */
    private function replaceQueryParts(array $parts): void
    {
        (\Closure::bind(function () use ($parts): void {
            $this->query = array_merge($this->query, $parts);
        }, $this->qb, \DbQuery::class))();
    }
}
