<?php

declare(strict_types=1);

namespace App\Framework\LiveComponents\Polling;

use App\Framework\LiveComponents\ValueObjects\ComponentId;

/**
 * Registry for tracking PollableClosure instances in templates.
 *
 * Maintains a mapping of PollIds to their pollable closure registrations
 * for automatic JavaScript generation and endpoint registration.
 */
final class PollableClosureRegistry
{
    /** @var array<string, PollableClosureRegistration> */
    private array $registrations = [];

    /**
     * Register a pollable closure.
     *
     * @param string $templateKey Template variable key (e.g., 'notifications')
     * @param PollableClosure $closure The pollable closure
     * @param ComponentId|null $componentId Optional component ID for scoping
     * @return PollableClosureRegistration
     */
    public function register(
        string $templateKey,
        PollableClosure $closure,
        ?ComponentId $componentId = null
    ): PollableClosureRegistration {
        $pollId = $this->generatePollId($templateKey, $componentId);

        $registration = new PollableClosureRegistration(
            pollId: $pollId,
            closure: $closure,
            templateKey: $templateKey,
            componentId: $componentId
        );

        $this->registrations[$pollId->value] = $registration;

        return $registration;
    }

    /**
     * Get a registration by poll ID.
     */
    public function get(PollId $pollId): ?PollableClosureRegistration
    {
        return $this->registrations[$pollId->value] ?? null;
    }

    /**
     * Check if a poll ID exists.
     */
    public function has(PollId $pollId): bool
    {
        return isset($this->registrations[$pollId->value]);
    }

    /**
     * Get all registered closures.
     *
     * @return array<PollableClosureRegistration>
     */
    public function getAll(): array
    {
        return array_values($this->registrations);
    }

    /**
     * Get all enabled registrations.
     *
     * @return array<PollableClosureRegistration>
     */
    public function getEnabled(): array
    {
        return array_values(array_filter(
            $this->registrations,
            fn(PollableClosureRegistration $reg) => $reg->shouldPoll()
        ));
    }

    /**
     * Get registrations for specific component.
     *
     * @return array<PollableClosureRegistration>
     */
    public function getForComponent(ComponentId $componentId): array
    {
        return array_values(array_filter(
            $this->registrations,
            fn(PollableClosureRegistration $reg) => $reg->isForComponent($componentId)
        ));
    }

    /**
     * Execute a closure by poll ID.
     *
     * @throws \RuntimeException if poll ID not found or closure throws
     */
    public function execute(PollId $pollId): mixed
    {
        $registration = $this->get($pollId);

        if ($registration === null) {
            throw new \RuntimeException("Poll ID '{$pollId}' not found");
        }

        try {
            return $registration->execute();
        } catch (\Throwable $e) {
            throw new \RuntimeException(
                "Failed to execute pollable closure '{$pollId}': {$e->getMessage()}",
                previous: $e
            );
        }
    }

    /**
     * Unregister a closure.
     */
    public function unregister(PollId $pollId): void
    {
        unset($this->registrations[$pollId->value]);
    }

    /**
     * Clear all registered closures.
     */
    public function clear(): void
    {
        $this->registrations = [];
    }

    /**
     * Get count of registered closures.
     */
    public function count(): int
    {
        return count($this->registrations);
    }

    /**
     * Generate unique poll ID.
     */
    private function generatePollId(string $templateKey, ?ComponentId $componentId): PollId
    {
        if ($componentId !== null) {
            return PollId::forClosure($templateKey, $componentId->toString());
        }

        return PollId::forClosure($templateKey);
    }

    /**
     * Get polling metadata for JavaScript generation.
     *
     * @return array<string, array{template_key: string, interval: int, event: ?string, endpoint: string, component_id: ?string}>
     */
    public function getPollingMetadata(): array
    {
        $metadata = [];

        foreach ($this->registrations as $pollIdString => $registration) {
            if (!$registration->shouldPoll()) {
                continue;
            }

            $metadata[$pollIdString] = $registration->getMetadata();
        }

        return $metadata;
    }
}
