<?php

declare(strict_types=1);

namespace App\Framework\Async;

use Fiber;
use SplQueue;

/**
 * Asynchrone Queue für Producer-Consumer Pattern
 */
final class AsyncQueue
{
    /** @var SplQueue<mixed> */
    private SplQueue $items;

    /** @var array<Fiber> */
    private array $waitingConsumers = [];

    private bool $closed = false;

    public function __construct(
        private readonly int $maxSize = 1000
    ) {
        $this->items = new SplQueue();
    }

    /**
     * Fügt ein Element zur Queue hinzu
     */
    public function enqueue(mixed $item): bool
    {
        if ($this->closed) {
            return false;
        }

        if ($this->items->count() >= $this->maxSize) {
            return false; // Queue voll
        }

        $this->items->enqueue($item);

        // Wecke wartende Consumer auf
        if (! empty($this->waitingConsumers)) {
            $consumer = array_shift($this->waitingConsumers);
            if (! $consumer->isTerminated()) {
                $consumer->resume($item);
            }
        }

        return true;
    }

    /**
     * Nimmt ein Element aus der Queue (blockierend)
     */
    public function dequeue(): mixed
    {
        if (! $this->items->isEmpty()) {
            return $this->items->dequeue();
        }

        if ($this->closed) {
            return null;
        }

        // Warte auf neues Element
        $fiber = new Fiber(function () {
            while ($this->items->isEmpty() && ! $this->closed) {
                Fiber::suspend();
            }

            if (! $this->items->isEmpty()) {
                return $this->items->dequeue();
            }

            return null;
        });

        $this->waitingConsumers[] = $fiber;
        $fiber->start();

        return $fiber->getReturn();
    }

    /**
     * Versucht ein Element zu nehmen (non-blocking)
     */
    public function tryDequeue(): mixed
    {
        if ($this->items->isEmpty()) {
            return null;
        }

        return $this->items->dequeue();
    }

    /**
     * Schließt die Queue
     */
    public function close(): void
    {
        $this->closed = true;

        // Wecke alle wartenden Consumer auf
        foreach ($this->waitingConsumers as $consumer) {
            if (! $consumer->isTerminated()) {
                $consumer->resume(null);
            }
        }
        $this->waitingConsumers = [];
    }

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

    /**
     * Gibt aktuelle Queue-Größe zurück
     */
    public function size(): int
    {
        return $this->items->count();
    }

    /**
     * Gibt Queue-Statistiken zurück
     */
    public function getStats(): array
    {
        return [
            'size' => $this->items->count(),
            'max_size' => $this->maxSize,
            'waiting_consumers' => count($this->waitingConsumers),
            'closed' => $this->closed,
        ];
    }
}
