<?php

declare(strict_types=1);

namespace App\Framework\Discovery\ValueObjects;

use App\Framework\Core\ValueObjects\Byte;
use App\Framework\Filesystem\ValueObjects\FilePath;
use ArrayIterator;
use Countable;
use IteratorAggregate;

/**
 * Immutable collection of TemplateMapping value objects
 *
 * Provides type-safe operations for template collections with filtering,
 * searching, and memory-efficient operations.
 */
final readonly class TemplateCollection implements Countable, IteratorAggregate
{
    /** @var array<TemplateMapping> */
    private array $templates;

    public function __construct(TemplateMapping ...$templates)
    {
        $this->templates = array_values($templates);
    }

    /**
     * Add a template to the collection
     */
    public function add(TemplateMapping $template): self
    {
        $templates = $this->templates;
        $templates[] = $template;

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

    /**
     * Add multiple templates to the collection
     */
    public function addMany(TemplateMapping ...$templates): self
    {
        return new self(...array_merge($this->templates, $templates));
    }

    /**
     * Filter templates by name pattern
     */
    public function filterByName(string $namePattern): self
    {
        $filtered = array_filter(
            $this->templates,
            fn (TemplateMapping $template) => $template->matchesName($namePattern)
        );

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

    /**
     * Filter templates by type
     */
    public function filterByType(string $type): self
    {
        $filtered = array_filter(
            $this->templates,
            fn (TemplateMapping $template) => $template->isType($type)
        );

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

    /**
     * Filter templates by directory
     */
    public function filterByDirectory(string $directory): self
    {
        $normalizedDirectory = rtrim($directory, '/');

        $filtered = array_filter(
            $this->templates,
            fn (TemplateMapping $template) => dirname($template->path->toString()) === $normalizedDirectory
        );

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

    /**
     * Filter templates by extension
     */
    public function filterByExtension(string $extension): self
    {
        $extension = ltrim($extension, '.');

        $filtered = array_filter(
            $this->templates,
            fn (TemplateMapping $template) => $template->getExtension() === $extension
        );

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

    /**
     * Find template by exact name and type
     */
    public function findExact(string $name, string $type = 'view'): ?TemplateMapping
    {
        foreach ($this->templates as $template) {
            if ($template->name === $name && $template->type === $type) {
                return $template;
            }
        }

        return null;
    }

    /**
     * Find template by name (any type)
     */
    public function findByName(string $name): ?TemplateMapping
    {
        foreach ($this->templates as $template) {
            if ($template->name === $name) {
                return $template;
            }
        }

        return null;
    }

    /**
     * Find template by file path
     */
    public function findByPath(FilePath $path): ?TemplateMapping
    {
        foreach ($this->templates as $template) {
            if ($template->path->equals($path)) {
                return $template;
            }
        }

        return null;
    }

    /**
     * Get templates grouped by type
     * @return array<string, self>
     */
    public function groupByType(): array
    {
        $grouped = [];

        foreach ($this->templates as $template) {
            if (! isset($grouped[$template->type])) {
                $grouped[$template->type] = [];
            }
            $grouped[$template->type][] = $template;
        }

        return array_map(
            fn (array $templates) => new self(...$templates),
            $grouped
        );
    }

    /**
     * Get templates grouped by directory
     * @return array<string, self>
     */
    public function groupByDirectory(): array
    {
        $grouped = [];

        foreach ($this->templates as $template) {
            $directory = dirname($template->path->toString());
            if (! isset($grouped[$directory])) {
                $grouped[$directory] = [];
            }
            $grouped[$directory][] = $template;
        }

        return array_map(
            fn (array $templates) => new self(...$templates),
            $grouped
        );
    }

    /**
     * Get unique template names
     * @return array<int, string>
     */
    public function getUniqueNames(): array
    {
        $names = array_map(fn (TemplateMapping $template) => $template->name, $this->templates);

        return array_unique($names);
    }

    /**
     * Get unique template types
     * @return array<int, string>
     */
    public function getUniqueTypes(): array
    {
        $types = array_map(fn (TemplateMapping $template) => $template->type, $this->templates);

        return array_unique($types);
    }

    /**
     * Get unique directories
     */
    public function getUniqueDirectories(): array
    {
        $directories = array_map(
            fn (TemplateMapping $template) => dirname($template->path->toString()),
            $this->templates
        );

        return array_unique($directories);
    }

    /**
     * Remove duplicate templates based on unique identifier
     */
    public function deduplicate(): self
    {
        $seen = [];
        $unique = [];

        foreach ($this->templates as $template) {
            $key = $template->getUniqueId();
            if (! isset($seen[$key])) {
                $seen[$key] = true;
                $unique[] = $template;
            }
        }

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

    /**
     * Sort templates by name
     */
    public function sortedByName(): self
    {
        $sorted = $this->templates;
        usort($sorted, fn (TemplateMapping $a, TemplateMapping $b) => $a->name <=> $b->name);

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

    /**
     * Sort templates by type, then by name
     */
    public function sortedByTypeAndName(): self
    {
        $sorted = $this->templates;
        usort($sorted, function (TemplateMapping $a, TemplateMapping $b) {
            $typeComparison = $a->type <=> $b->type;

            return $typeComparison !== 0 ? $typeComparison : $a->name <=> $b->name;
        });

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

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

    /**
     * Get first template or null
     */
    public function first(): ?TemplateMapping
    {
        return $this->templates[0] ?? null;
    }

    /**
     * Get last template or null
     */
    public function last(): ?TemplateMapping
    {
        return end($this->templates) ?: null;
    }

    /**
     * Convert to array of TemplateMapping objects
     */
    public function toArray(): array
    {
        return $this->templates;
    }

    /**
     * Convert to legacy array format for backward compatibility
     */
    public function toLegacyArray(): array
    {
        $legacy = [];

        foreach ($this->templates as $template) {
            // Group by template name with variants by type
            if (! isset($legacy[$template->name])) {
                $legacy[$template->name] = [];
            }
            $legacy[$template->name][$template->type] = $template->path->toString();
        }

        return $legacy;
    }

    /**
     * Get memory footprint of entire collection
     */
    public function getMemoryFootprint(): Byte
    {
        $totalBytes = 0;
        foreach ($this->templates as $template) {
            $totalBytes += $template->getMemoryFootprint()->toBytes();
        }

        // Add overhead for collection structure
        $totalBytes += count($this->templates) * 8; // approximate pointer overhead

        return Byte::fromBytes($totalBytes);
    }

    // Countable interface implementation

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

    // IteratorAggregate interface implementation

    public function getIterator(): ArrayIterator
    {
        return new ArrayIterator($this->templates);
    }
}
