<?php

declare(strict_types=1);

namespace App\Framework\LiveComponents\ValueObjects;

use App\Framework\LiveComponents\Contracts\LiveComponentContract;
use App\Framework\View\Contracts\StaticComponent;

/**
 * Component Name Map - Registry von Component Namen zu Class Namen
 *
 * Verwaltet Mapping zwischen:
 * - LiveComponent Namen (aus #[LiveComponent(name: 'counter')])
 * - StaticComponent Namen (aus #[ComponentName('button')])
 * zu ihren vollqualifizierten Klassennamen.
 *
 * Value Object Pattern für Type Safety und Unveränderlichkeit.
 */
final readonly class ComponentNameMap
{
    /** @var array<string, class-string<LiveComponentContract|StaticComponent>> */
    private array $map;

    /**
     * @param ComponentMapping ...$mappings
     */
    public function __construct(ComponentMapping ...$mappings)
    {
        $map = [];

        foreach ($mappings as $mapping) {
            // Prüfe auf Name-Kollision
            if (isset($map[$mapping->name])) {
                throw new \RuntimeException(
                    "Component name collision: '{$mapping->name}' is already registered. " .
                    "Existing: {$map[$mapping->name]}, New: {$mapping->className}"
                );
            }

            $map[$mapping->name] = $mapping->className;
        }

        $this->map = $map;
    }

    /**
     * Leere Map erstellen
     */
    public static function empty(): self
    {
        return new self();
    }

    /**
     * Komponente zur Map hinzufügen
     *
     * @param string $componentName z.B. "counter", "search-bar"
     * @param class-string<LiveComponentContract|StaticComponent> $className
     */
    public function withComponent(string $componentName, string $className): self
    {
        // Erstelle neues Mapping
        $newMapping = new ComponentMapping($componentName, $className);

        // Erstelle neues Array mit allen existierenden Mappings plus neuem
        $mappings = [];
        foreach ($this->map as $name => $class) {
            $mappings[] = new ComponentMapping($name, $class);
        }
        $mappings[] = $newMapping;

        return new self(...$mappings);
    }

    /**
     * Klassennamen für Komponenten-Namen abrufen
     *
     * @return class-string<LiveComponentContract|StaticComponent>|null
     */
    public function getClassName(string $componentName): ?string
    {
        return $this->map[$componentName] ?? null;
    }

    /**
     * Prüfen ob Komponente registriert ist
     */
    public function has(string $componentName): bool
    {
        return isset($this->map[$componentName]);
    }

    /**
     * Alle registrierten Komponenten-Namen abrufen
     *
     * @return array<int, string>
     */
    public function getAllNames(): array
    {
        return array_keys($this->map);
    }

    /**
     * Anzahl registrierter Komponenten
     */
    public function count(): int
    {
        return count($this->map);
    }

    /**
     * Prüfen ob Komponente ein LiveComponent ist
     */
    public function isLiveComponent(string $componentName): bool
    {
        $className = $this->getClassName($componentName);

        if ($className === null) {
            return false;
        }

        return is_subclass_of($className, LiveComponentContract::class);
    }

    /**
     * Prüfen ob Komponente ein StaticComponent ist
     */
    public function isStaticComponent(string $componentName): bool
    {
        $className = $this->getClassName($componentName);

        if ($className === null) {
            return false;
        }

        return is_subclass_of($className, StaticComponent::class);
    }

    /**
     * Alle LiveComponent Namen abrufen
     *
     * @return array<int, string>
     */
    public function getLiveComponentNames(): array
    {
        return array_filter(
            $this->getAllNames(),
            fn(string $name) => $this->isLiveComponent($name)
        );
    }

    /**
     * Alle StaticComponent Namen abrufen
     *
     * @return array<int, string>
     */
    public function getStaticComponentNames(): array
    {
        return array_filter(
            $this->getAllNames(),
            fn(string $name) => $this->isStaticComponent($name)
        );
    }

    /**
     * Zu Array konvertieren
     *
     * @return array<string, class-string<LiveComponentContract|StaticComponent>>
     */
    public function toArray(): array
    {
        return $this->map;
    }

    /**
     * Statistiken über Registry
     */
    public function getStats(): array
    {
        return [
            'total' => $this->count(),
            'live_components' => count($this->getLiveComponentNames()),
            'static_components' => count($this->getStaticComponentNames()),
            'components' => $this->getAllNames(),
        ];
    }
}
