<?php

declare(strict_types=1);

namespace App\Framework\Cache;

use Countable;
use IteratorAggregate;
use Traversable;

/**
 * Collection of cache identifiers for batch operations
 */
final readonly class CacheIdentifierCollection implements Countable, IteratorAggregate
{
    /**
     * @param array<CacheIdentifier> $identifiers
     */
    private function __construct(
        private array $identifiers
    ) {
    }

    /**
     * Create from CacheIdentifiers using spread operator
     */
    public static function fromIdentifiers(CacheIdentifier ...$identifiers): self
    {
        return new self($identifiers);
    }

    /**
     * Create empty collection
     */
    public static function empty(): self
    {
        return new self([]);
    }

    /**
     * Get all identifiers as array
     * @return array<CacheIdentifier>
     */
    public function getIdentifiers(): array
    {
        return $this->identifiers;
    }

    /**
     * Get only cache keys
     */
    public function getKeys(): CacheKeyCollection
    {
        $keys = array_filter(
            $this->identifiers,
            fn (CacheIdentifier $id) => $id instanceof CacheKey
        );

        return CacheKeyCollection::fromKeys(...$keys);
    }

    /**
     * Get only cache tags
     * @return array<CacheTag>
     */
    public function getTags(): array
    {
        return array_filter(
            $this->identifiers,
            fn (CacheIdentifier $id) => $id instanceof CacheTag
        );
    }

    /**
     * Get only cache prefixes
     * @return array<CachePrefix>
     */
    public function getPrefixes(): array
    {
        return array_filter(
            $this->identifiers,
            fn (CacheIdentifier $id) => $id instanceof CachePrefix
        );
    }

    /**
     * Filter by identifier type
     */
    public function filterByType(CacheIdentifierType $type): self
    {
        $filtered = array_filter(
            $this->identifiers,
            fn (CacheIdentifier $id) => $id->getType() === $type
        );

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

    /**
     * Check if collection contains specific identifier
     */
    public function contains(CacheIdentifier $identifier): bool
    {
        foreach ($this->identifiers as $existing) {
            if ($existing->equals($identifier)) {
                return true;
            }
        }

        return false;
    }

    /**
     * Filter identifiers by predicate
     */
    public function filter(callable $predicate): self
    {
        $filtered = array_filter($this->identifiers, $predicate);

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

    /**
     * Map over identifiers
     */
    public function map(callable $mapper): self
    {
        $mapped = array_map($mapper, $this->identifiers);

        return new self($mapped);
    }

    /**
     * Add another identifier collection
     */
    public function merge(self $other): self
    {
        return new self(array_merge($this->identifiers, $other->identifiers));
    }

    /**
     * Add a single identifier
     */
    public function add(CacheIdentifier $identifier): self
    {
        return new self([...$this->identifiers, $identifier]);
    }

    /**
     * Remove duplicates based on string representation
     */
    public function unique(): self
    {
        $unique = [];
        $seen = [];

        foreach ($this->identifiers as $identifier) {
            $key = $identifier->getType()->value . ':' . $identifier->toString();
            if (! isset($seen[$key])) {
                $unique[] = $identifier;
                $seen[$key] = true;
            }
        }

        return new self($unique);
    }

    /**
     * Check if collection is empty
     */
    public function isEmpty(): bool
    {
        return empty($this->identifiers);
    }

    /**
     * Get identifiers as string array
     * @return array<string>
     */
    public function toStringArray(): array
    {
        return array_map(
            fn (CacheIdentifier $id) => $id->toString(),
            $this->identifiers
        );
    }

    /**
     * Group identifiers by type
     * @return array<string, array<CacheIdentifier>>
     */
    public function groupByType(): array
    {
        $groups = [];

        foreach ($this->identifiers as $identifier) {
            $type = $identifier->getType()->value;
            $groups[$type] ??= [];
            $groups[$type][] = $identifier;
        }

        return $groups;
    }

    /**
     * Countable implementation
     */
    public function count(): int
    {
        return count($this->identifiers);
    }

    /**
     * IteratorAggregate implementation
     * @return Traversable<int, CacheIdentifier>
     */
    public function getIterator(): Traversable
    {
        foreach ($this->identifiers as $identifier) {
            yield $identifier;
        }
    }
}
