<?php
namespace Incoviba\Common\Implement\Database\Query;

use Incoviba\Common\Define;
use Incoviba\Common\Ideal;

class Select extends Ideal\Query implements Define\Query\Select
{
    protected array $columns;
    protected string $table;
    protected array $joins;
    protected array $conditions;
    protected array $groups;
    protected array $haves;
    protected array $orders;
    protected int $limit;
    protected int $offset;

    public function columns(array|string $expressions): Select
    {
        if (is_string($expressions)) {
            return $this->addColumn($expressions);
        }
        foreach ($expressions as $expression) {
            $this->addColumn($expression);
        }
        return $this;
    }
    public function from(string $table): Select
    {
        $this->table = $table;
        return $this;
    }
    public function joined(array|string $joins): Select
    {
        if (is_string($joins)) {
            return $this->addJoin($joins);
        }
        foreach ($joins as $join) {
            $this->addJoin($join);
        }
        return $this;
    }
    public function where(array|string $conditions): Select
    {
        if (is_string($conditions)) {
            return $this->addCondition($conditions);
        }
        foreach ($conditions as $condition) {
            $this->addCondition($condition);
        }
        return $this;
    }
    public function group(array|string $grouping): Select
    {
        if (is_string($grouping)) {
            return $this->addGroup($grouping);
        }
        foreach ($grouping as $group) {
            $this->addGroup($group);
        }
        return $this;
    }
    public function having(array|string $conditions): Select
    {
        if (is_string($conditions)) {
            return $this->addCondition($conditions);
        }
        foreach ($conditions as $condition) {
            $this->addCondition($condition);
        }
        return $this;
    }
    public function order(array|string $sorting): Select
    {
        if (is_string($sorting)) {
            return $this->addOrder($sorting);
        }
        foreach ($sorting as $order) {
            $this->addOrder($order);
        }
        return $this;
    }
    public function limit(int $limit, ?int $offset = null): Select
    {
        $this->limit = $limit;
        if ($offset !== null) {
            return $this->offset($offset);
        }
        return $this;
    }
    public function offset(int $offset): Select
    {
        $this->offset = $offset;
        return $this;
    }
    public function build(): string
    {
        $query = [
            "SELECT {$this->getColumns()} FROM {$this->table}",
            $this->getJoins(),
            $this->getConditions(),
            $this->getGroups(),
            $this->getHaving(),
            $this->getOrder(),
            $this->getLimit()
        ];
        return implode('', $query);
    }

    protected function addColumn(string $expression): Select
    {
        if (!isset($this->columns)) {
            $this->columns = [];
        }
        $this->columns []= $expression;
        return $this;
    }
    protected function addJoin(string $join): Select
    {
        if (!isset($this->joins)) {
            $this->joins = [];
        }
        $this->joins []= $join;
        return $this;
    }
    protected function addCondition(string $condition): Select
    {
        if (!isset($this->conditions)) {
            $this->conditions = [];
        }
        $this->conditions []= $condition;
        return $this;
    }
    protected function addGroup(string $group): Select
    {
        if (!isset($this->groups)) {
            $this->groups = [];
        }
        $this->groups []= $group;
        return $this;
    }
    protected function addHaving(string $having): Select
    {
        if (!isset($this->haves)) {
            $this->haves = [];
        }
        $this->haves []= $having;
        return $this;
    }
    protected function addOrder(string $order): Select
    {
        if (!isset($this->orders)) {
            $this->orders = [];
        }
        $this->orders []= $order;
        return $this;
    }
    protected function getColumns(): string
    {
        if (!isset($this->columns) or count($this->columns) === 0) {
            return '*';
        }
        return implode(', ', $this->columns);
    }
    protected function getJoins(): string
    {
        if (!isset($this->joins) or count($this->joins) === 0) {
            return '';
        }
        return ' ' . implode(' ', $this->joins);
    }
    protected function getConditions(): string
    {
        if (!isset($this->conditions) or count($this->conditions) === 0) {
            return '';
        }
        return ' WHERE ' . implode(' AND ', $this->conditions);
    }
    protected function getGroups(): string
    {
        if (!isset($this->groups) or count($this->groups) === 0) {
            return '';
        }
        return ' GROUP BY ' . implode(', ', $this->groups);
    }
    protected function getHaving(): string
    {
        if (!isset($this->haves) or count($this->haves) === 0) {
            return '';
        }
        return ' HAVING ' . implode(' AND ', $this->haves);
    }
    protected function getOrder(): string
    {
        if (!isset($this->orders) or count($this->orders) === 0) {
            return '';
        }
        return ' ORDER BY ' . implode(', ', $this->orders);
    }
    protected function getLimit(): string
    {
        if (!isset($this->limit) or $this->limit <= 0) {
            return '';
        }
        return " LIMIT {$this->limit}{$this->getOffset()}";
    }
    protected function getOffset(): string
    {
        if (!isset($this->offset) or $this->offset <= 0) {
            return '';
        }
        return " OFFSET {$this->offset}";
    }
}
