<?php

declare(strict_types=1);

namespace App\Framework\Router\ValueObjects;

/**
 * Value Object für Parameter-Constraints und Validation Rules
 */
final readonly class ParameterConstraints
{
    /**
     * @param array<string, array{
     *     type?: string,
     *     required?: bool,
     *     min?: int|float,
     *     max?: int|float,
     *     minLength?: int,
     *     maxLength?: int,
     *     pattern?: string,
     *     enum?: array<mixed>,
     *     default?: mixed
     * }> $constraints
     */
    public function __construct(
        private array $constraints = []
    ) {
    }

    /**
     * Factory method für häufige Constraint-Typen
     */
    public static function create(): self
    {
        return new self();
    }

    /**
     * String-Parameter mit optionaler Längen-Beschränkung
     */
    public function string(string $name, bool $required = true, ?int $minLength = null, ?int $maxLength = null, ?string $pattern = null): self
    {
        $constraint = [
            'type' => 'string',
            'required' => $required,
        ];

        if ($minLength !== null) {
            $constraint['minLength'] = $minLength;
        }

        if ($maxLength !== null) {
            $constraint['maxLength'] = $maxLength;
        }

        if ($pattern !== null) {
            $constraint['pattern'] = $pattern;
        }

        return $this->with($name, $constraint);
    }

    /**
     * Integer-Parameter mit optionaler Range-Beschränkung
     */
    public function int(string $name, bool $required = true, ?int $min = null, ?int $max = null): self
    {
        $constraint = [
            'type' => 'int',
            'required' => $required,
        ];

        if ($min !== null) {
            $constraint['min'] = $min;
        }

        if ($max !== null) {
            $constraint['max'] = $max;
        }

        return $this->with($name, $constraint);
    }

    /**
     * Float-Parameter mit optionaler Range-Beschränkung
     */
    public function float(string $name, bool $required = true, ?float $min = null, ?float $max = null): self
    {
        $constraint = [
            'type' => 'float',
            'required' => $required,
        ];

        if ($min !== null) {
            $constraint['min'] = $min;
        }

        if ($max !== null) {
            $constraint['max'] = $max;
        }

        return $this->with($name, $constraint);
    }

    /**
     * Boolean-Parameter
     */
    public function bool(string $name, bool $required = true): self
    {
        return $this->with($name, [
            'type' => 'bool',
            'required' => $required,
        ]);
    }

    /**
     * Array-Parameter
     */
    public function array(string $name, bool $required = true): self
    {
        return $this->with($name, [
            'type' => 'array',
            'required' => $required,
        ]);
    }

    /**
     * Enum-Parameter (erlaubte Werte)
     */
    public function enum(string $name, array $allowedValues, bool $required = true): self
    {
        return $this->with($name, [
            'enum' => $allowedValues,
            'required' => $required,
        ]);
    }

    /**
     * ID-Parameter (positive Integer)
     */
    public function id(string $name, bool $required = true): self
    {
        return $this->int($name, $required, 1);
    }

    /**
     * Slug-Parameter (alphanumeric + hyphens)
     */
    public function slug(string $name, bool $required = true): self
    {
        return $this->string($name, $required, 1, 255, '/^[a-z0-9\-]+$/');
    }

    /**
     * Email-Parameter
     */
    public function email(string $name, bool $required = true): self
    {
        return $this->string($name, $required, 3, 254, '/^[^\s@]+@[^\s@]+\.[^\s@]+$/');
    }

    /**
     * UUID-Parameter
     */
    public function uuid(string $name, bool $required = true): self
    {
        return $this->string($name, $required, 36, 36, '/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i');
    }

    /**
     * URL-Parameter
     */
    public function url(string $name, bool $required = true): self
    {
        return $this->string($name, $required, 3, 2048, '/^https?:\/\/.+/');
    }

    /**
     * Datum-Parameter (YYYY-MM-DD)
     */
    public function date(string $name, bool $required = true): self
    {
        return $this->string($name, $required, 10, 10, '/^\d{4}-\d{2}-\d{2}$/');
    }

    /**
     * DateTime-Parameter (ISO 8601)
     */
    public function dateTime(string $name, bool $required = true): self
    {
        return $this->string($name, $required, 19, 25, '/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d{3})?Z?$/');
    }

    /**
     * Page-Parameter für Pagination
     */
    public function page(string $name = 'page', int $maxPage = 9999): self
    {
        return $this->int($name, false, 1, $maxPage);
    }

    /**
     * Limit-Parameter für Pagination
     */
    public function limit(string $name = 'limit', int $defaultLimit = 20, int $maxLimit = 100): self
    {
        return $this->int($name, false, 1, $maxLimit)->withDefault($name, $defaultLimit);
    }

    /**
     * Sort-Parameter für Sortierung
     */
    public function sort(string $name = 'sort', array $allowedFields = []): self
    {
        if (! empty($allowedFields)) {
            return $this->enum($name, $allowedFields, false);
        }

        return $this->string($name, false, 1, 50);
    }

    /**
     * Search-Parameter
     */
    public function search(string $name = 'q', int $minLength = 2, int $maxLength = 255): self
    {
        return $this->string($name, false, $minLength, $maxLength);
    }

    /**
     * Custom Constraint hinzufügen
     */
    public function custom(string $name, array $constraint): self
    {
        return $this->with($name, $constraint);
    }

    /**
     * Default-Wert setzen
     */
    public function withDefault(string $name, mixed $defaultValue): self
    {
        if (! isset($this->constraints[$name])) {
            throw new \InvalidArgumentException("Parameter '{$name}' not found in constraints");
        }

        $constraints = $this->constraints;
        $constraints[$name]['default'] = $defaultValue;

        return new self($constraints);
    }

    /**
     * Neue Instanz mit zusätzlichem Constraint
     */
    private function with(string $name, array $constraint): self
    {
        $constraints = $this->constraints;
        $constraints[$name] = $constraint;

        return new self($constraints);
    }

    /**
     * Alle Constraints als Array
     */
    public function toArray(): array
    {
        return $this->constraints;
    }

    /**
     * Prüft ob Constraint für Parameter existiert
     */
    public function has(string $name): bool
    {
        return isset($this->constraints[$name]);
    }

    /**
     * Holt Constraint für Parameter
     */
    public function get(string $name): ?array
    {
        return $this->constraints[$name] ?? null;
    }

    /**
     * Anzahl der Constraints
     */
    public function count(): int
    {
        return count($this->constraints);
    }

    /**
     * Prüft ob leer
     */
    public function isEmpty(): bool
    {
        return empty($this->constraints);
    }

    /**
     * Parameter-Namen mit Constraints
     */
    public function getParameterNames(): array
    {
        return array_keys($this->constraints);
    }

    /**
     * Required Parameter-Namen
     */
    public function getRequiredParameters(): array
    {
        return array_keys(array_filter($this->constraints, fn ($constraint) => $constraint['required'] ?? false));
    }

    /**
     * Optional Parameter-Namen
     */
    public function getOptionalParameters(): array
    {
        return array_keys(array_filter($this->constraints, fn ($constraint) => ! ($constraint['required'] ?? false)));
    }

    /**
     * Debug-Informationen
     */
    public function getDebugInfo(): array
    {
        return [
            'total_constraints' => $this->count(),
            'required_parameters' => $this->getRequiredParameters(),
            'optional_parameters' => $this->getOptionalParameters(),
            'parameter_types' => array_map(fn ($constraint) => $constraint['type'] ?? 'mixed', $this->constraints),
        ];
    }
}
