<?php

declare(strict_types=1);

namespace App\Framework\Async;

use Fiber;
use SplQueue;

/**
 * Pool für begrenzte parallele Fiber-Ausführung
 */
final class AsyncPool
{
    /** @var SplQueue<callable> */
    private SplQueue $pendingOperations;

    /** @var array<string, Fiber> */
    private array $activeFibers = [];

    /** @var array<string, mixed> */
    private array $results = [];

    public function __construct(
        private readonly FiberManager $fiberManager,
        private readonly int $maxConcurrency = 10
    ) {
        $this->pendingOperations = new SplQueue();
    }

    /**
     * Fügt eine Operation zum Pool hinzu
     */
    public function add(callable $operation, ?string $id = null): string
    {
        $id ??= uniqid('pool_', true);
        $this->pendingOperations->enqueue(['id' => $id, 'operation' => $operation]);

        return $id;
    }

    /**
     * Führt alle Operationen mit begrenzter Parallelität aus
     */
    public function execute(): array
    {
        while (! $this->pendingOperations->isEmpty() || ! empty($this->activeFibers)) {
            // Starte neue Fibers bis zur maximalen Parallelität
            while (count($this->activeFibers) < $this->maxConcurrency && ! $this->pendingOperations->isEmpty()) {
                $task = $this->pendingOperations->dequeue();
                $this->startFiber($task['id'], $task['operation']);
            }

            // Sammle abgeschlossene Fibers
            $this->collectCompletedFibers();

            // Kurze Pause um CPU zu schonen
            if (! empty($this->activeFibers)) {
                usleep(100); // 0.1ms
            }
        }

        return $this->results;
    }

    private function startFiber(string $id, callable $operation): void
    {
        try {
            $fiber = $this->fiberManager->async($operation, $id);
            $this->activeFibers[$id] = $fiber;
        } catch (\Throwable $e) {
            // If fiber throws immediately on start, store as result
            $this->results[$id] = $e;
        }
    }

    private function collectCompletedFibers(): void
    {
        foreach ($this->activeFibers as $id => $fiber) {
            if ($fiber->isTerminated()) {
                try {
                    $this->results[$id] = $fiber->getReturn();
                } catch (\Throwable $e) {
                    $this->results[$id] = $e;
                }
                unset($this->activeFibers[$id]);
            }
        }
    }

    /**
     * Wartet auf ein spezifisches Ergebnis
     */
    public function await(string $id): mixed
    {
        while (! isset($this->results[$id])) {
            if (isset($this->activeFibers[$id])) {
                $this->collectCompletedFibers();
                usleep(100);
            } else {
                throw new \RuntimeException("Operation with ID '$id' not found");
            }
        }

        return $this->results[$id];
    }

    /**
     * Gibt aktuelle Pool-Statistiken zurück
     */
    public function getStats(): array
    {
        return [
            'pending' => $this->pendingOperations->count(),
            'active' => count($this->activeFibers),
            'completed' => count($this->results),
            'max_concurrency' => $this->maxConcurrency,
        ];
    }
}
