<?php

declare(strict_types=1);

namespace App\Framework\Async;

use Fiber;
use SplQueue;

/**
 * Mutex für Thread-sichere Operationen zwischen Fibers
 */
final class AsyncMutex
{
    private bool $locked = false;

    /** @var SplQueue<Fiber> */
    private SplQueue $waitingFibers;

    private ?int $owner = null;

    public function __construct(
        private readonly string $name = ''
    ) {
        $this->waitingFibers = new SplQueue();
    }

    /**
     * Erwirbt das Lock (blockierend)
     */
    public function acquire(): void
    {
        $currentFiber = Fiber::getCurrent();
        $fiberId = spl_object_id($currentFiber);

        if (! $this->locked) {
            $this->locked = true;
            $this->owner = $fiberId;

            return;
        }

        // Wenn bereits gelockt, warte in der Queue
        $fiber = new Fiber(function () use ($fiberId) {
            while ($this->locked) {
                Fiber::suspend();
            }
            $this->locked = true;
            $this->owner = $fiberId;
        });

        $this->waitingFibers->enqueue($fiber);
        $fiber->start();
        $fiber->getReturn();
    }

    /**
     * Versucht das Lock zu erwerben (non-blocking)
     */
    public function tryAcquire(): bool
    {
        if ($this->locked) {
            return false;
        }

        $currentFiber = Fiber::getCurrent();
        $this->locked = true;
        $this->owner = spl_object_id($currentFiber);

        return true;
    }

    /**
     * Gibt das Lock frei
     */
    public function release(): void
    {
        $currentFiber = Fiber::getCurrent();
        $fiberId = spl_object_id($currentFiber);

        if ($this->owner !== $fiberId) {
            throw new \RuntimeException("Cannot release mutex owned by different fiber");
        }

        $this->locked = false;
        $this->owner = null;

        // Wecke nächsten wartenden Fiber auf
        if (! $this->waitingFibers->isEmpty()) {
            $nextFiber = $this->waitingFibers->dequeue();
            if (! $nextFiber->isTerminated()) {
                $nextFiber->resume();
            }
        }
    }

    /**
     * Führt eine Funktion mit automatischem Lock aus
     */
    public function synchronized(callable $callback): mixed
    {
        $this->acquire();

        try {
            return $callback();
        } finally {
            $this->release();
        }
    }

    /**
     * Prüft ob das Lock gehalten wird
     */
    public function isLocked(): bool
    {
        return $this->locked;
    }

    /**
     * Gibt Mutex-Statistiken zurück
     */
    public function getStats(): array
    {
        return [
            'name' => $this->name,
            'locked' => $this->locked,
            'owner' => $this->owner,
            'waiting_fibers' => $this->waitingFibers->count(),
        ];
    }
}
