<?php

declare(strict_types=1);

namespace App\Framework\Discovery\Results;

use App\Framework\Core\ValueObjects\Byte;
use App\Framework\Discovery\ValueObjects\DiscoveredAttribute;
use Countable;

/**
 * Memory-optimized registry for attribute discoveries using Value Objects
 * Pure Value Object implementation without legacy array support
 */
final class AttributeRegistry implements Countable
{
    /** @var array<string, DiscoveredAttribute[]> */
    private array $mappings = [];

    private bool $isOptimized = false;

    public function __construct()
    {
    }

    /**
     * Convert to array for cache serialization
     */
    public function toArray(): array
    {
        $serializedMappings = [];

        foreach ($this->mappings as $attributeClass => $mappings) {
            $serializedMappings[$attributeClass] = [];
            foreach ($mappings as $mapping) {
                $serializedMappings[$attributeClass][] = $mapping->toArray();
            }
        }

        return [
            'mappings' => $serializedMappings,
        ];
    }

    /**
     * Create AttributeRegistry from array data (for cache deserialization)
     * Always loads as non-optimized to ensure data integrity
     */
    public static function fromArray(array $data): self
    {
        $registry = new self();

        $mappings = [];
        foreach (($data['mappings'] ?? []) as $attributeClass => $mappingArrays) {
            $mappings[$attributeClass] = [];

            foreach ($mappingArrays as $mappingArray) {
                try {
                    $mappings[$attributeClass][] = DiscoveredAttribute::fromArray($mappingArray);
                } catch (\Throwable $e) {
                    // Log error for debugging
                    error_log('⚠️ Failed to deserialize DiscoveredAttribute: ' . $e->getMessage());
                    error_log('   Attribute Class: ' . $attributeClass);
                    error_log('   Data keys: ' . implode(', ', array_keys($mappingArray)));

                    // Skip corrupted cache entries - they'll be recreated on next discovery
                    continue;
                }
            }
        }

        $registry->mappings = $mappings;
        $registry->isOptimized = false; // Always force re-optimization for cache data integrity

        return $registry;
    }

    /**
     * Add a value object mapping
     */
    public function add(string $attributeClass, DiscoveredAttribute $attribute): void
    {
        if (! isset($this->mappings[$attributeClass])) {
            $this->mappings[$attributeClass] = [];
        }

        $this->mappings[$attributeClass][] = $attribute;
        $this->isOptimized = false;
    }

    /**
     * Get discovered attributes as Value Objects
     * @return DiscoveredAttribute[]
     */
    public function get(string $attributeClass): array
    {
        return $this->mappings[$attributeClass] ?? [];
    }

    public function has(string $attributeClass): bool
    {
        return ! empty($this->mappings[$attributeClass]);
    }

    public function getCount(string $attributeClass): int
    {
        return count($this->mappings[$attributeClass] ?? []);
    }

    /**
     * Count total mappings across all attribute types (Countable interface)
     */
    public function count(): int
    {
        return array_sum(array_map('count', $this->mappings));
    }

    public function getAllTypes(): array
    {
        return array_keys($this->mappings);
    }

    public function optimize(): void
    {
        if ($this->isOptimized) {
            return;
        }

        // Deduplicate using Value Object unique IDs
        foreach ($this->mappings as $attributeClass => &$mappings) {
            $mappings = $this->deduplicateAttributes($mappings);
        }
        unset($mappings);

        $this->mappings = array_filter($this->mappings);
        $this->isOptimized = true;
    }

    public function clear(): void
    {
        $this->mappings = [];
        $this->isOptimized = false;
    }

    public function clearCache(): void
    {
        // No caches to clear in this implementation
    }

    public function getMemoryStats(): array
    {
        $totalMappings = array_sum(array_map('count', $this->mappings));
        $totalMemory = $this->getTotalMemoryFootprint();

        return [
            'types' => count($this->mappings),
            'instances' => $totalMappings,
            'estimated_bytes' => $totalMemory->toBytes(),
            'memory_footprint' => $totalMemory->toHumanReadable(),
            'is_optimized' => $this->isOptimized,
            'value_objects' => true,
        ];
    }

    /**
     * Deduplicate attributes using Value Object unique IDs
     * @param DiscoveredAttribute[] $attributes
     * @return DiscoveredAttribute[]
     */
    private function deduplicateAttributes(array $attributes): array
    {
        $seen = [];
        $deduplicated = [];

        foreach ($attributes as $attribute) {
            $uniqueId = $attribute->getUniqueId();

            if (! isset($seen[$uniqueId])) {
                $seen[$uniqueId] = true;
                $deduplicated[] = $attribute;
            }
        }

        return $deduplicated;
    }

    /**
     * Find attributes by class name
     * @return DiscoveredAttribute[]
     */
    public function findByClass(string $className): array
    {
        $results = [];
        foreach ($this->mappings as $mappings) {
            foreach ($mappings as $attribute) {
                if ($attribute->className->getFullyQualified() === $className) {
                    $results[] = $attribute;
                }
            }
        }

        return $results;
    }

    /**
     * Get total memory footprint
     */
    public function getTotalMemoryFootprint(): Byte
    {
        $total = Byte::zero();
        foreach ($this->mappings as $attributes) {
            foreach ($attributes as $attribute) {
                $total = $total->add($attribute->getMemoryFootprint());
            }
        }

        return $total;
    }
}
