<?php

declare(strict_types=1);

namespace App\Framework\Database\Platform\ValueObjects;

use App\Framework\Database\Platform\Enums\ColumnType;

/**
 * Represents a database column definition
 */
final readonly class ColumnDefinition
{
    public function __construct(
        public string $name,
        public ColumnType $type,
        public ?int $length = null,
        public ?int $precision = null,
        public ?int $scale = null,
        public bool $nullable = true,
        public mixed $default = null,
        public bool $autoIncrement = false,
        public bool $unsigned = false,
        public string $charset = 'utf8mb4',
        public string $collation = 'utf8mb4_unicode_ci',
        public ?string $comment = null,
        public ?ColumnOptions $options = null
    ) {
        if ($type->requiresLength() && $length === null) {
            throw new \InvalidArgumentException("Column type {$type->value} requires a length");
        }

        if ($type->requiresPrecision() && $precision === null) {
            throw new \InvalidArgumentException("Column type {$type->value} requires precision");
        }

        if ($autoIncrement && ! $type->isNumeric()) {
            throw new \InvalidArgumentException("Auto-increment is only valid for numeric column types");
        }

        if ($unsigned && ! $type->isNumeric()) {
            throw new \InvalidArgumentException("Unsigned attribute is only valid for numeric column types");
        }
    }

    public function getOptions(): ColumnOptions
    {
        return $this->options ?? ColumnOptions::default();
    }

    public static function id(string $name = 'id'): self
    {
        return new self(
            name: $name,
            type: ColumnType::BIG_INTEGER,
            nullable: false,
            autoIncrement: true,
            unsigned: true
        );
    }

    public static function string(string $name, int $length = 255, bool $nullable = true): self
    {
        return new self(
            name: $name,
            type: ColumnType::VARCHAR,
            length: $length,
            nullable: $nullable
        );
    }

    public static function text(string $name, bool $nullable = true): self
    {
        return new self(
            name: $name,
            type: ColumnType::TEXT,
            nullable: $nullable
        );
    }

    public static function integer(string $name, bool $nullable = true, bool $unsigned = false): self
    {
        return new self(
            name: $name,
            type: ColumnType::INTEGER,
            nullable: $nullable,
            unsigned: $unsigned
        );
    }

    public static function bigInteger(string $name, bool $nullable = true, bool $unsigned = false): self
    {
        return new self(
            name: $name,
            type: ColumnType::BIG_INTEGER,
            nullable: $nullable,
            unsigned: $unsigned
        );
    }

    public static function decimal(string $name, int $precision = 8, int $scale = 2, bool $nullable = true): self
    {
        return new self(
            name: $name,
            type: ColumnType::DECIMAL,
            precision: $precision,
            scale: $scale,
            nullable: $nullable
        );
    }

    public static function boolean(string $name, bool $nullable = true, ?bool $default = null): self
    {
        return new self(
            name: $name,
            type: ColumnType::BOOLEAN,
            nullable: $nullable,
            default: $default
        );
    }

    public static function timestamp(string $name, bool $nullable = true): self
    {
        return new self(
            name: $name,
            type: ColumnType::TIMESTAMP,
            nullable: $nullable
        );
    }

    public static function timestamps(): array
    {
        return [
            new self(
                name: 'created_at',
                type: ColumnType::TIMESTAMP,
                nullable: false,
                default: 'CURRENT_TIMESTAMP'
            ),
            new self(
                name: 'updated_at',
                type: ColumnType::TIMESTAMP,
                nullable: false,
                default: 'CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP'
            ),
        ];
    }

    public static function json(string $name, bool $nullable = true): self
    {
        return new self(
            name: $name,
            type: ColumnType::JSON,
            nullable: $nullable
        );
    }

    public static function uuid(string $name, bool $nullable = true): self
    {
        return new self(
            name: $name,
            type: ColumnType::UUID,
            nullable: $nullable
        );
    }

    public static function binary(string $name, int $length, bool $nullable = true): self
    {
        return new self(
            name: $name,
            type: ColumnType::BINARY,
            length: $length,
            nullable: $nullable
        );
    }

    public static function date(string $name, bool $nullable = true): self
    {
        return new self(
            name: $name,
            type: ColumnType::DATE,
            nullable: $nullable
        );
    }

    public static function time(string $name, bool $nullable = true): self
    {
        return new self(
            name: $name,
            type: ColumnType::TIME,
            nullable: $nullable
        );
    }

    public static function datetime(string $name, bool $nullable = true): self
    {
        return new self(
            name: $name,
            type: ColumnType::DATETIME,
            nullable: $nullable
        );
    }

    public static function year(string $name, bool $nullable = true): self
    {
        return new self(
            name: $name,
            type: ColumnType::YEAR,
            nullable: $nullable
        );
    }

    public static function float(string $name, bool $nullable = true): self
    {
        return new self(
            name: $name,
            type: ColumnType::FLOAT,
            nullable: $nullable
        );
    }

    public function withDefault(mixed $value): self
    {
        return new self(
            name: $this->name,
            type: $this->type,
            length: $this->length,
            precision: $this->precision,
            scale: $this->scale,
            nullable: $this->nullable,
            default: $value,
            autoIncrement: $this->autoIncrement,
            unsigned: $this->unsigned,
            charset: $this->charset,
            collation: $this->collation,
            comment: $this->comment,
            options: $this->options
        );
    }

    public function withComment(string $comment): self
    {
        return new self(
            name: $this->name,
            type: $this->type,
            length: $this->length,
            precision: $this->precision,
            scale: $this->scale,
            nullable: $this->nullable,
            default: $this->default,
            autoIncrement: $this->autoIncrement,
            unsigned: $this->unsigned,
            charset: $this->charset,
            collation: $this->collation,
            comment: $comment,
            options: $this->options
        );
    }

    public function notNull(): self
    {
        return $this->notNullable();
    }

    public function notNullable(): self
    {
        return new self(
            name: $this->name,
            type: $this->type,
            length: $this->length,
            precision: $this->precision,
            scale: $this->scale,
            nullable: false,
            default: $this->default,
            autoIncrement: $this->autoIncrement,
            unsigned: $this->unsigned,
            charset: $this->charset,
            collation: $this->collation,
            comment: $this->comment,
            options: $this->options
        );
    }

    public function unique(): self
    {
        $currentOptions = $this->getOptions();
        $newOptions = $currentOptions->withUnique(true);

        return new self(
            name: $this->name,
            type: $this->type,
            length: $this->length,
            precision: $this->precision,
            scale: $this->scale,
            nullable: $this->nullable,
            default: $this->default,
            autoIncrement: $this->autoIncrement,
            unsigned: $this->unsigned,
            charset: $this->charset,
            collation: $this->collation,
            comment: $this->comment,
            options: $newOptions
        );
    }

    public function hasDefault(): bool
    {
        return $this->default !== null;
    }

    public function isPrimaryKey(): bool
    {
        return $this->autoIncrement && $this->type->isNumeric();
    }
}
