<?php

declare(strict_types=1);

namespace Gls\GlsPoland\Soap\Engine\Metadata\Collection;

use Gls\GlsPoland\Soap\Engine\Exception\MetadataException;
use Gls\GlsPoland\Soap\Engine\Metadata\Model\Type;

/**
 * @implements \IteratorAggregate<int, Type>
 */
final class TypeCollection implements \Countable, \IteratorAggregate
{
    /**
     * @var list<Type>
     */
    private $types;

    /**
     * @no-named-arguments
     */
    public function __construct(Type ...$types)
    {
        $this->types = $types;
    }

    /**
     * @return \Iterator<int, Type>
     */
    public function getIterator(): \Iterator
    {
        return new \ArrayIterator($this->types);
    }

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

    /**
     * @template T
     *
     * @param callable(Type): T $callback
     *
     * @return array<T>
     */
    public function map(callable $callback): array
    {
        return array_map($callback, $this->types);
    }

    /**
     * @param callable(Type): bool|null $filter
     */
    public function filter(?callable $filter = null): self
    {
        return new self(...array_filter(
            $this->types,
            $filter ?? static function ($value): bool {
                return !empty($value);
            },
            null === $filter ? ARRAY_FILTER_USE_BOTH : 0
        ));
    }

    /**
     * @psalm-suppress MixedReturnStatement
     * @psalm-suppress MixedInferredReturnType
     *
     * @template T
     *
     * @param callable(T, Type): T $reducer
     * @param mixed $initial
     *
     * @return T
     */
    public function reduce(callable $reducer, $initial)
    {
        return array_reduce(
            $this->types,
            $reducer,
            $initial
        );
    }

    /**
     * @throws MetadataException
     */
    public function getFirstByName(string $name): Type
    {
        foreach ($this->types as $type) {
            if ($name === $type->getName()) {
                return $type;
            }
        }

        throw MetadataException::typeNotFound($name);
    }
}
