<?php

declare(strict_types=1);

namespace App\Framework\Filesystem;

use App\Framework\Core\ValueObjects\Timestamp;
use App\Framework\Filesystem\ValueObjects\FileCollection;
use App\Framework\Filesystem\ValueObjects\FilePath;
use App\Framework\Filesystem\ValueObjects\FilePattern;
use App\Framework\Logging\Logger;
use App\Framework\Performance\MemoryMonitor;
use FilesystemIterator;
use Generator;
use RecursiveDirectoryIterator;
use RecursiveIteratorIterator;
use RegexIterator;
use Throwable;

/**
 * Generic file scanner for filesystem operations
 * Moved from Discovery module to be reusable across the framework
 */
class FileScanner implements FileScannerInterface
{
    private ?Logger $logger;

    private MemoryMonitor $memoryMonitor;

    private FileSystemService $fileSystemService;

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

    /**
     * Find all files matching a pattern in a directory
     */
    public function findFiles(FilePath $directoryPath, ?FilePattern $pattern = null): FileCollection
    {
        if (! $directoryPath->exists()) {
            return FileCollection::empty();
        }

        try {
            $files = [];
            $iterator = new RecursiveIteratorIterator(
                new RecursiveDirectoryIterator($directoryPath->toString(), FilesystemIterator::SKIP_DOTS),
                RecursiveIteratorIterator::LEAVES_ONLY
            );

            if ($pattern !== null) {
                $iterator = new RegexIterator($iterator, $pattern->toRegex());
            }

            foreach ($iterator as $fileInfo) {
                try {
                    $file = File::fromSplFileInfo($fileInfo);
                    $files[] = $file;
                } catch (Throwable $e) {
                    $this->logger?->warning("Skipping file {$fileInfo->getPathname()}: {$e->getMessage()}");

                    continue;
                }
            }

            return FileCollection::fromFiles($files);

        } catch (Throwable $e) {
            $this->logger?->error("Failed to scan directory {$directoryPath->toString()}: {$e->getMessage()}");

            return FileCollection::empty();
        }
    }

    /**
     * Get the latest modification time of all files matching pattern
     */
    public function getLatestModificationTime(FilePath $directoryPath, ?FilePattern $pattern = null): Timestamp
    {
        $files = $this->findFiles($directoryPath, $pattern);

        if ($files->isEmpty()) {
            return Timestamp::now();
        }

        $latestTime = 0;
        foreach ($files as $file) {
            $modTime = $file->getModificationTime()->toFloat();
            if ($modTime > $latestTime) {
                $latestTime = $modTime;
            }
        }

        return Timestamp::fromFloat($latestTime);
    }

    /**
     * Find files that have been modified since a given timestamp
     */
    public function findFilesModifiedSince(FilePath $directoryPath, Timestamp $since, ?FilePattern $pattern = null): FileCollection
    {
        $allFiles = $this->findFiles($directoryPath, $pattern);

        return $allFiles->modifiedAfter($since);
    }

    /**
     * Stream files one by one for memory-efficient processing
     * @return Generator<File>
     */
    public function streamFiles(FilePath $directoryPath, ?FilePattern $pattern = null): Generator
    {
        if (! $directoryPath->exists()) {
            return;
        }

        try {
            $iterator = new RecursiveIteratorIterator(
                new RecursiveDirectoryIterator($directoryPath->toString(), FilesystemIterator::SKIP_DOTS),
                RecursiveIteratorIterator::LEAVES_ONLY
            );

            if ($pattern !== null) {
                $iterator = new RegexIterator($iterator, $pattern->toRegex());
            }

            foreach ($iterator as $fileInfo) {
                try {
                    yield File::fromSplFileInfo($fileInfo);
                } catch (Throwable $e) {
                    $this->logger?->warning("Skipping file {$fileInfo->getPathname()}: {$e->getMessage()}");

                    continue;
                }
            }

        } catch (Throwable $e) {
            $this->logger?->error("Failed to scan directory {$directoryPath->toString()}: {$e->getMessage()}");
        }
    }

    /**
     * Stream files in batches for balanced memory/performance
     * @return Generator<FileCollection>
     */
    public function streamFilesInBatches(FilePath $directoryPath, ?FilePattern $pattern = null, int $batchSize = 100): Generator
    {
        $batch = [];
        $count = 0;

        foreach ($this->streamFiles($directoryPath, $pattern) as $file) {
            $batch[] = $file;
            $count++;

            if ($count >= $batchSize) {
                yield FileCollection::fromFiles($batch);
                $batch = [];
                $count = 0;
            }
        }

        // Yield remaining files
        if (! empty($batch)) {
            yield FileCollection::fromFiles($batch);
        }
    }
}
