<?php

declare(strict_types=1);

namespace App\Framework\Database\ValueObjects;

use App\Framework\Database\Validation\DatabaseIdentifierValidator;
use InvalidArgumentException;

/**
 * Value Object representing a validated database schema name
 *
 * Schemas are a PostgreSQL concept for organizing database objects.
 * MySQL uses databases instead of schemas, but this VO can be used
 * for cross-database compatibility.
 *
 * Ensures schema names follow SQL naming conventions:
 * - Start with letter or underscore
 * - Contain only alphanumeric characters and underscores
 * - Maximum 64 characters (PostgreSQL standard)
 * - No SQL injection patterns
 *
 * Immutable and type-safe replacement for string schema names.
 */
final readonly class SchemaName
{
    /**
     * @param string $value Validated schema name
     * @throws InvalidArgumentException if name is invalid
     */
    private function __construct(
        public string $value
    ) {}

    /**
     * Create SchemaName from string
     *
     * @throws InvalidArgumentException if name is invalid
     */
    public static function fromString(string $name): self
    {
        $validator = new DatabaseIdentifierValidator();
        $validator->validate($name, 'schema');

        return new self($name);
    }

    /**
     * Create default public schema (PostgreSQL default)
     */
    public static function public(): self
    {
        return new self('public');
    }

    /**
     * Create information_schema (system schema)
     */
    public static function informationSchema(): self
    {
        return new self('information_schema');
    }

    /**
     * Create pg_catalog (PostgreSQL system catalog)
     */
    public static function pgCatalog(): self
    {
        return new self('pg_catalog');
    }

    /**
     * Get schema name as string
     */
    public function toString(): string
    {
        return $this->value;
    }

    /**
     * Get quoted schema name for SQL
     *
     * @param string $platform Database platform (postgresql, mysql, sqlite)
     */
    public function quoted(string $platform = 'postgresql'): string
    {
        return match (strtolower($platform)) {
            'postgresql', 'postgres', 'pgsql' => '"' . $this->value . '"',
            'sqlite' => '"' . $this->value . '"',
            default => '`' . $this->value . '`', // MySQL
        };
    }

    /**
     * Get qualified table name (schema.table)
     *
     * @param TableName $table Table name to qualify
     * @param string $platform Database platform
     */
    public function qualifyTable(TableName $table, string $platform = 'postgresql'): string
    {
        return $this->quoted($platform) . '.' . $table->quoted($platform);
    }

    /**
     * Check if schema name equals another
     *
     * Comparison is case-sensitive for PostgreSQL schemas
     */
    public function equals(self $other): bool
    {
        return $this->value === $other->value;
    }

    /**
     * Check if schema name matches a pattern
     *
     * Pattern supports wildcards (*) for matching
     * Example: 'app_*' matches 'app_v1', 'app_v2'
     */
    public function matches(string $pattern): bool
    {
        // Replace * wildcards with placeholder, quote the pattern, then restore wildcards
        $placeholder = '___WILDCARD___';
        $pattern = str_replace('*', $placeholder, $pattern);
        $regex = '/^' . preg_quote($pattern, '/') . '$/';
        $regex = str_replace($placeholder, '.*', $regex);

        return preg_match($regex, $this->value) === 1;
    }

    /**
     * Check if schema name is a reserved SQL keyword
     */
    public function isReservedKeyword(): bool
    {
        $validator = new DatabaseIdentifierValidator();
        return $validator->isReservedKeyword($this->value);
    }

    /**
     * Convert to lowercase
     */
    public function toLower(): string
    {
        return strtolower($this->value);
    }

    /**
     * Check if schema name has a prefix
     */
    public function hasPrefix(string $prefix): bool
    {
        return str_starts_with($this->value, $prefix);
    }

    /**
     * Check if schema name has a suffix
     */
    public function hasSuffix(string $suffix): bool
    {
        return str_ends_with($this->value, $suffix);
    }

    /**
     * Check if this is the default public schema
     */
    public function isPublic(): bool
    {
        return $this->value === 'public';
    }

    /**
     * Check if this is a system schema
     *
     * System schemas: information_schema, pg_catalog, pg_*
     */
    public function isSystemSchema(): bool
    {
        return $this->value === 'information_schema'
            || $this->value === 'pg_catalog'
            || str_starts_with($this->value, 'pg_');
    }

    /**
     * Check if this is a temporary schema
     *
     * Temporary schemas typically have 'temp' or 'tmp' in the name
     */
    public function isTemporary(): bool
    {
        return str_contains(strtolower($this->value), 'temp')
            || str_contains(strtolower($this->value), 'tmp');
    }

    /**
     * Add prefix to schema name (returns new instance)
     */
    public function withPrefix(string $prefix): self
    {
        return self::fromString($prefix . $this->value);
    }

    /**
     * Remove prefix from schema name (returns new instance)
     */
    public function withoutPrefix(string $prefix): self
    {
        if (!$this->hasPrefix($prefix)) {
            return $this; // Optimization: return same instance if no change
        }

        return self::fromString(substr($this->value, strlen($prefix)));
    }

    /**
     * Magic method for string conversion
     */
    public function __toString(): string
    {
        return $this->value;
    }
}
