<?php

declare(strict_types=1);

namespace App\Framework\HttpClient\Curl;

/**
 * Pool for executing multiple cURL requests with concurrency control
 *
 * Limits the number of parallel requests to prevent resource exhaustion.
 * Automatically batches requests when the queue exceeds the concurrency limit.
 *
 * Features:
 * - Configurable concurrency limit
 * - Automatic batching of requests
 * - Progress tracking
 * - Memory efficient execution
 *
 * Example:
 * ```php
 * $pool = new MultiHandlePool(maxConcurrent: 5);
 *
 * foreach ($urls as $url) {
 *     $pool->add(new Handle($url));
 * }
 *
 * $results = $pool->executeAll(); // Max 5 requests at a time
 * ```
 */
final class MultiHandlePool
{
    /**
     * Queued handles waiting for execution
     *
     * @var array<Handle>
     */
    private array $queue = [];

    /**
     * Handles currently being executed
     *
     * @var array<Handle>
     */
    private array $active = [];

    /**
     * Completed handles
     *
     * @var array<Handle>
     */
    private array $completed = [];

    /**
     * Create a new pool with concurrency limit
     *
     * @param int $maxConcurrent Maximum number of parallel requests (default: 10)
     */
    public function __construct(
        private readonly int $maxConcurrent = 10
    ) {
        if ($maxConcurrent < 1) {
            throw new \InvalidArgumentException('Max concurrent must be at least 1');
        }
    }

    /**
     * Add a handle to the execution queue
     *
     * @param Handle $handle Handle to execute
     * @return self For method chaining
     */
    public function add(Handle $handle): self
    {
        $this->queue[] = $handle;
        return $this;
    }

    /**
     * Execute all queued handles with concurrency control
     *
     * Executes requests in batches based on maxConcurrent limit.
     * Returns all completed handles in order of completion.
     *
     * @return array<Handle> All completed handles
     */
    public function executeAll(): array
    {
        $this->completed = [];

        while (!empty($this->queue) || !empty($this->active)) {
            // Fill active batch up to concurrency limit
            while (!empty($this->queue) && count($this->active) < $this->maxConcurrent) {
                $this->active[] = array_shift($this->queue);
            }

            // Execute current batch
            if (!empty($this->active)) {
                $this->executeBatch();
            }
        }

        return $this->completed;
    }

    /**
     * Execute current batch of active handles
     */
    private function executeBatch(): void
    {
        // Create multi handle for this batch
        $multi = new MultiHandle(...$this->active);

        // Execute all handles in batch
        $completedBatch = $multi->executeAll();

        // Move completed handles to completed array
        $this->completed = array_merge($this->completed, $completedBatch);

        // Clear active handles
        $this->active = [];
    }

    /**
     * Get total number of queued handles
     *
     * @return int
     */
    public function queueSize(): int
    {
        return count($this->queue);
    }

    /**
     * Get number of active handles
     *
     * @return int
     */
    public function activeCount(): int
    {
        return count($this->active);
    }

    /**
     * Get number of completed handles
     *
     * @return int
     */
    public function completedCount(): int
    {
        return count($this->completed);
    }

    /**
     * Get total number of handles (queued + active + completed)
     *
     * @return int
     */
    public function totalCount(): int
    {
        return count($this->queue) + count($this->active) + count($this->completed);
    }

    /**
     * Get progress as percentage (0-100)
     *
     * @return float
     */
    public function getProgress(): float
    {
        $total = $this->totalCount();

        if ($total === 0) {
            return 100.0;
        }

        return ($this->completedCount() / $total) * 100.0;
    }

    /**
     * Check if execution is complete
     *
     * @return bool
     */
    public function isComplete(): bool
    {
        return empty($this->queue) && empty($this->active);
    }

    /**
     * Clear all handles (queued, active, and completed)
     *
     * @return self
     */
    public function clear(): self
    {
        $this->queue = [];
        $this->active = [];
        $this->completed = [];
        return $this;
    }

    /**
     * Get the configured concurrency limit
     *
     * @return int
     */
    public function getMaxConcurrent(): int
    {
        return $this->maxConcurrent;
    }
}
