<?php

declare(strict_types=1);

namespace App\Framework\Discovery\Processing;

use App\Framework\Discovery\Contracts\DiscoveryProcessor;
use App\Framework\Discovery\Contracts\DiscoveryVisitor;
use App\Framework\Discovery\DiscoveryDataCollector;
use App\Framework\Discovery\Results\DiscoveryRegistry;
use App\Framework\Discovery\ValueObjects\DiscoveryContext;
use App\Framework\Discovery\ValueObjects\FileContext;
use App\Framework\Filesystem\File;
use App\Framework\Filesystem\ValueObjects\FilePath;
use App\Framework\Filesystem\FileScanner;
use App\Framework\Filesystem\FileSystemService;
use App\Framework\Filesystem\ValueObjects\FilePattern;
use App\Framework\Logging\Logger;
use App\Framework\Performance\MemoryMonitor;
use Generator;
use Throwable;

/**
 * Processes files for discovery using a stream-based approach
 */
final readonly class FileProcessor implements DiscoveryProcessor
{
    private ClassExtractor $classExtractor;

    public function __construct(
        private FileScanner $scanner,
        private FileSystemService $fileSystemService,
        private ?Logger $logger = null,
        private ?MemoryMonitor $memoryMonitor = null
    ) {
        $this->classExtractor = new ClassExtractor($fileSystemService);
    }

    public function process(DiscoveryContext $context, array $plugins): DiscoveryRegistry
    {
        $collector = new DiscoveryDataCollector();
        $visitors = $this->extractVisitors($plugins, $context);

        // Initialize all visitors
        foreach ($visitors as $visitor) {
            $visitor->reset();
        }

        // Process files in a streaming manner
        foreach ($this->streamFiles($context) as $file) {
            $this->processFile($file, $visitors, $collector, $context);

            // Memory management
            if ($context->getProcessedFiles() % 50 === 0) {
                gc_collect_cycles();
            }
        }

        // Build final registry
        $registry = $collector->createRegistry();
        $collector->clear();

        return $registry;
    }

    public function getHealthStatus(): array
    {
        return [
            'processor' => 'healthy',
            'scanner' => 'operational',
            'memory_usage' => $this->memoryMonitor?->getCurrentUsage() ?? 'N/A',
        ];
    }

    /**
     * Stream files for processing
     * @return Generator<File>
     */
    private function streamFiles(DiscoveryContext $context): Generator
    {
        foreach ($context->paths as $path) {
            $filePath = FilePath::create($path);

            // Stream files one by one
            foreach ($this->scanner->streamFiles($filePath, FilePattern::php()) as $file) {
                // Apply exclude patterns
                if ($this->shouldExcludeFile($file, $context->options->excludePatterns)) {
                    continue;
                }

                yield $file;
            }
        }
    }

    /**
     * Process a single file with all visitors
     */
    private function processFile(
        File $file,
        array $visitors,
        DiscoveryDataCollector $collector,
        DiscoveryContext $context
    ): void {
        try {
            // Extract classes from file
            $classNames = $this->classExtractor->extractFromFile($file);
            $fileContext = FileContext::fromFile($file)->withClassNames($classNames);

            // Let each visitor process the file
            foreach ($visitors as $visitor) {
                if (! $visitor->shouldProcessFile($file)) {
                    continue;
                }

                // Visit file
                $visitor->visitFile($file, $fileContext);

                // Visit each class
                foreach ($classNames as $className) {
                    $visitor->visitClass($className, $fileContext);
                }
            }

            // Collect results from visitors
            $this->collectVisitorResults($visitors, $collector);

            $context->incrementProcessedFiles();

        } catch (Throwable $e) {
            $this->logger?->warning(
                "Failed to process file {$file->getPath()->toString()}: {$e->getMessage()}"
            );
        }
    }

    /**
     * Extract visitors from plugins
     * @return array<DiscoveryVisitor>
     */
    private function extractVisitors(array $plugins, DiscoveryContext $context): array
    {
        $visitors = [];

        foreach ($plugins as $plugin) {
            if (! $plugin->isEnabled($context)) {
                continue;
            }

            foreach ($plugin->getVisitors() as $visitor) {
                $visitors[] = $visitor;
            }
        }

        return $visitors;
    }

    /**
     * Collect results from visitors into the collector
     */
    private function collectVisitorResults(array $visitors, DiscoveryDataCollector $collector): void
    {
        // This would be implemented based on the specific visitor types
        // For now, it's a placeholder for the collection logic
    }

    /**
     * Check if file should be excluded
     */
    private function shouldExcludeFile(File $file, array $excludePatterns): bool
    {
        if (empty($excludePatterns)) {
            return false;
        }

        $path = $file->getPath()->toString();

        return array_any($excludePatterns, fn ($pattern) => fnmatch($pattern, $path));

    }
}
