<?php

declare(strict_types=1);

namespace App\Framework\Database\Criteria;

/**
 * Query builder that converts Criteria to SQL
 */
final class CriteriaQuery
{
    public function __construct(
        public readonly Criteria $criteria,
        public readonly string $tableName
    ) {
    }

    /**
     * Build the complete SQL query
     */
    public function toSql(): string
    {
        $sql = 'SELECT ' . $this->buildSelectClause();
        $sql .= ' FROM ' . $this->tableName;

        $whereClause = $this->buildWhereClause();
        if ($whereClause) {
            $sql .= ' WHERE ' . $whereClause;
        }

        $orderClause = $this->buildOrderClause();
        if ($orderClause) {
            $sql .= ' ORDER BY ' . $orderClause;
        }

        $limitClause = $this->buildLimitClause();
        if ($limitClause) {
            $sql .= ' ' . $limitClause;
        }

        return $sql;
    }

    /**
     * Get all parameters for prepared statement
     */
    public function getParameters(): array
    {
        $parameters = [];

        foreach ($this->criteria->getCriteria() as $criterion) {
            $parameters = array_merge($parameters, $criterion->getParameters());
        }

        return $parameters;
    }

    private function buildSelectClause(): string
    {
        $projection = $this->criteria->getProjection();

        return $projection ? $projection->toSql() : '*';
    }

    private function buildWhereClause(): string
    {
        $criteria = $this->criteria->getCriteria();

        if (empty($criteria)) {
            return '';
        }

        if (count($criteria) === 1) {
            return $criteria[0]->toSql();
        }

        // Multiple criteria are combined with AND
        $conditions = array_map(fn ($c) => $c->toSql(), $criteria);

        return '(' . implode(') AND (', $conditions) . ')';
    }

    private function buildOrderClause(): string
    {
        $orders = $this->criteria->getOrders();

        if (empty($orders)) {
            return '';
        }

        return implode(', ', array_map(fn ($o) => $o->toSql(), $orders));
    }

    private function buildLimitClause(): string
    {
        $maxResults = $this->criteria->getMaxResults();
        $firstResult = $this->criteria->getFirstResult();

        if ($maxResults === null && $firstResult === null) {
            return '';
        }

        $clause = 'LIMIT';

        if ($firstResult !== null) {
            $clause .= " {$firstResult},";
        }

        if ($maxResults !== null) {
            $clause .= " {$maxResults}";
        } else {
            $clause .= " 18446744073709551615"; // MySQL max value
        }

        return $clause;
    }
}
