<?php

declare(strict_types=1);

namespace App\Framework\Reflection\Collections;

use App\Framework\Reflection\WrappedReflectionParameter;
use ArrayIterator;
use Countable;
use IteratorAggregate;

/**
 * @implements IteratorAggregate<int, WrappedReflectionParameter>
 */
final readonly class ParameterCollection implements IteratorAggregate, Countable
{
    /** @var WrappedReflectionParameter[] */
    private array $parameters;

    public function __construct(WrappedReflectionParameter ...$parameters)
    {
        $this->parameters = $parameters;
    }

    /**
     * @return ArrayIterator<int, WrappedReflectionParameter>
     */
    public function getIterator(): ArrayIterator
    {
        return new ArrayIterator(array_values($this->parameters));
    }

    public function count(): int
    {
        return count($this->parameters);
    }

    public function isEmpty(): bool
    {
        return empty($this->parameters);
    }

    /**
     * @return array<WrappedReflectionParameter>
     */
    public function toArray(): array
    {
        return $this->parameters;
    }

    public function getByPosition(int $position): ?WrappedReflectionParameter
    {
        return $this->parameters[$position] ?? null;
    }

    public function getByName(string $name): ?WrappedReflectionParameter
    {
        foreach ($this->parameters as $parameter) {
            if ($parameter->getName() === $name) {
                return $parameter;
            }
        }

        return null;
    }

    public function hasParameter(string $name): bool
    {
        return $this->getByName($name) !== null;
    }

    public function getRequired(): self
    {
        $required = [];
        foreach ($this->parameters as $parameter) {
            if (! $parameter->isOptional()) {
                $required[] = $parameter;
            }
        }

        return new self(...$required);
    }

    public function getOptional(): self
    {
        $optional = [];
        foreach ($this->parameters as $parameter) {
            if ($parameter->isOptional()) {
                $optional[] = $parameter;
            }
        }

        return new self(...$optional);
    }

    public function getTyped(): self
    {
        $typed = [];
        foreach ($this->parameters as $parameter) {
            if ($parameter->getTypeName() !== null) {
                $typed[] = $parameter;
            }
        }

        return new self(...$typed);
    }

    public function getByType(string $typeName): self
    {
        $filtered = [];
        foreach ($this->parameters as $parameter) {
            if ($parameter->getTypeName() === $typeName) {
                $filtered[] = $parameter;
            }
        }

        return new self(...$filtered);
    }

    /**
     * @return array<array<string, mixed>>
     */
    public function toInfoArray(): array
    {
        return array_map(fn ($param) => $param->toArray(), $this->parameters);
    }
}
