<?php
declare(strict_types=1);

namespace Celena\Core\Database;

use Celena\Core\Database\Driver\DriverInterface;

/**
 * Декларативное описание структуры таблицы. Компилируется в DDL под конкретный драйвер.
 */
final class Blueprint
{
    /** @var array<int, array<string, mixed>> */
    private array $columns = [];
    /** @var array<int, array{type:string, columns:string[], name:?string}> */
    private array $indexes = [];

    public function __construct(public readonly string $table)
    {
    }

    public function id(string $name = 'id'): self
    {
        $this->columns[] = ['name' => $name, 'type' => 'id', 'options' => [], 'nullable' => false, 'default' => null];
        return $this;
    }

    public function string(string $name, int $length = 255): self
    {
        return $this->add($name, 'string', ['length' => $length]);
    }

    public function text(string $name): self        { return $this->add($name, 'text'); }
    public function integer(string $name): self     { return $this->add($name, 'integer'); }
    public function bigInteger(string $name): self  { return $this->add($name, 'bigInteger'); }
    public function boolean(string $name): self     { return $this->add($name, 'boolean'); }
    public function json(string $name): self        { return $this->add($name, 'json'); }
    public function datetime(string $name): self    { return $this->add($name, 'datetime'); }
    public function date(string $name): self        { return $this->add($name, 'date'); }
    public function float(string $name): self       { return $this->add($name, 'float'); }

    public function decimal(string $name, int $precision = 10, int $scale = 2): self
    {
        return $this->add($name, 'decimal', ['precision' => $precision, 'scale' => $scale]);
    }

    public function nullable(): self
    {
        $this->lastColumn()['nullable'] = true;
        return $this;
    }

    public function default(mixed $value): self
    {
        $this->lastColumn()['default'] = $value;
        return $this;
    }

    public function unique(string|array $columns, ?string $name = null): self
    {
        $cols = is_array($columns) ? $columns : [$columns];
        $this->indexes[] = ['type' => 'unique', 'columns' => $cols, 'name' => $name];
        return $this;
    }

    public function index(string|array $columns, ?string $name = null): self
    {
        $cols = is_array($columns) ? $columns : [$columns];
        $this->indexes[] = ['type' => 'index', 'columns' => $cols, 'name' => $name];
        return $this;
    }

    public function timestamps(): self
    {
        return $this->datetime('created_at')->nullable()->datetime('updated_at')->nullable();
    }

    /** @return array<int, string> Список SQL-выражений (CREATE TABLE + индексы) */
    public function toSql(DriverInterface $driver): array
    {
        $cols = [];
        foreach ($this->columns as $c) {
            $type = $driver->mapType($c['type'], $c['options'] ?? []);
            $line = $driver->quoteId($c['name']) . ' ' . $type;
            if ($c['type'] !== 'id') {
                $line .= $c['nullable'] ? ' NULL' : ' NOT NULL';
                if ($c['default'] !== null) {
                    $line .= ' DEFAULT ' . $this->literal($c['default']);
                }
            }
            $cols[] = $line;
        }

        $sql = ['CREATE TABLE ' . $driver->quoteId($this->table) . ' (' . implode(', ', $cols) . ')'];

        foreach ($this->indexes as $idx) {
            $name = $idx['name'] ?? ($this->table . '_' . implode('_', $idx['columns']) . '_' . $idx['type']);
            $columnsSql = implode(', ', array_map(fn ($c) => $driver->quoteId($c), $idx['columns']));
            $unique = $idx['type'] === 'unique' ? 'UNIQUE ' : '';
            $sql[] = 'CREATE ' . $unique . 'INDEX ' . $driver->quoteId($name)
                . ' ON ' . $driver->quoteId($this->table) . ' (' . $columnsSql . ')';
        }
        return $sql;
    }

    private function add(string $name, string $type, array $options = []): self
    {
        $this->columns[] = [
            'name' => $name,
            'type' => $type,
            'options' => $options,
            'nullable' => false,
            'default' => null,
        ];
        return $this;
    }

    private function &lastColumn(): array
    {
        return $this->columns[array_key_last($this->columns)];
    }

    private function literal(mixed $value): string
    {
        if (is_bool($value)) {
            return $value ? 'TRUE' : 'FALSE';
        }
        if (is_int($value) || is_float($value)) {
            return (string) $value;
        }
        if ($value === null) {
            return 'NULL';
        }
        return "'" . str_replace("'", "''", (string) $value) . "'";
    }
}
