<?php

declare(strict_types=1);

namespace Butschster\ContextGenerator\Source\File;

use Butschster\ContextGenerator\Config\Exclude\ExcludeRegistryInterface;
use Butschster\ContextGenerator\Lib\Finder\FinderInterface;
use Butschster\ContextGenerator\Lib\Finder\FinderResult;
use Butschster\ContextGenerator\Lib\TreeBuilder\FileTreeBuilder;
use Butschster\ContextGenerator\Source\Fetcher\FilterableSourceInterface;
use Symfony\Component\Finder\Finder;
use Symfony\Component\Finder\SplFileInfo;

/**
 * Implementation of FinderInterface using Symfony's Finder component
 */
final readonly class SymfonyFinder implements FinderInterface
{
    public function __construct(
        private ExcludeRegistryInterface $excludeRegistry,
        private FileTreeBuilder $fileTreeBuilder = new FileTreeBuilder(),
    ) {}

    /**
     * Find files based on the given source configuration
     *
     * @param FilterableSourceInterface $source Source configuration with filter criteria
     * @param string $basePath Optional base path to normalize file paths in the tree view
     * @param array $options Additional options for the finder
     * @return FinderResult The result containing found files and tree view
     */
    public function find(FilterableSourceInterface $source, string $basePath = '', array $options = []): FinderResult
    {
        $finder = new Finder();
        $finder->files();

        // Configure in() with directories and files
        $directories = $source->in();
        $files = $source->files();

        if (!empty($directories)) {
            $finder->in($directories);
        }

        if (!empty($files)) {
            $finder->append($files);
        }

        // Configure name pattern for file matching
        $namePattern = $source->name();
        if ($namePattern !== null) {
            $finder->name($namePattern);
        }

        // Configure path pattern
        $pathPattern = $source->path();
        if ($pathPattern !== null) {
            $finder->path($pathPattern);
        }

        // Configure notPath pattern
        $notPathPattern = $source->notPath();
        if ($notPathPattern !== null) {
            $finder->notPath($notPathPattern);
        }

        // Configure contains pattern
        $containsPattern = $source->contains();
        if ($containsPattern !== null) {
            $finder->contains($containsPattern);
        }

        // Configure notContains pattern
        $notContainsPattern = $source->notContains();
        if ($notContainsPattern !== null) {
            $finder->notContains($notContainsPattern);
        }

        // Configure size constraints
        $sizeConstraints = $source->size();
        if ($sizeConstraints !== null) {
            $finder->size($sizeConstraints);
        }

        // Configure date constraints
        $dateConstraints = $source->date();
        if ($dateConstraints !== null) {
            $finder->date($dateConstraints);
        }

        // Configure ignoreUnreadableDirs
        if ($source->ignoreUnreadableDirs()) {
            $finder->ignoreUnreadableDirs();
        }

        // Apply depth constraint if maxDepth is set
        if (isset($options['maxDepth']) && $options['maxDepth'] > 0) {
            $finder->depth('<= ' . $options['maxDepth']);
        }

        $finder->sortByName();

        // Check for maxFiles limit
        $maxFiles = $source->maxFiles();
        $hasLimit = $maxFiles !== null && $maxFiles > 0;

        // Generate tree view (always on all files for consistency)
        $treeView = $this->generateTreeView($finder, $basePath, $options);

        // Get files with limit if needed
        if ($hasLimit) {
            $limitedFiles = [];
            $count = 0;

            foreach ($finder as $file) {
                $limitedFiles[] = $file;
                $count++;

                if ($count >= $maxFiles) {
                    break;
                }
            }

            return new FinderResult(
                files: $limitedFiles,
                treeView: $treeView,
            );
        }

        // No limit, filter out excluded files
        $files = [];
        foreach ($finder as $file) {
            // Skip files that would be excluded by path patterns
            if ($this->shouldExcludeFile($this->getPath($file, $basePath))) {
                continue;
            }

            $files[] = $file;
        }

        // Return filtered files
        return new FinderResult(
            files: $files,
            treeView: $treeView,
        );
    }

    /**
     * Generate a tree view of the found files
     *
     * @param Finder $finder The Symfony Finder instance with results
     * @param string $basePath Optional base path to normalize file paths
     * @param array $options Additional options for tree view generation
     * @return string Text representation of the file tree
     */
    private function generateTreeView(Finder $finder, string $basePath, array $options): string
    {
        $filePaths = [];

        foreach ($finder as $file) {
            // Skip excluded files in tree view
            if ($this->shouldExcludeFile($this->getPath($file, $basePath))) {
                continue;
            }

            $filePaths[] = $file->getRealPath();
        }

        if (empty($filePaths)) {
            return "No files found.\n";
        }

        return $this->fileTreeBuilder->buildTree($filePaths, $basePath, $options);
    }

    /**
     * Check if a file should be excluded based on global exclusion patterns
     */
    private function shouldExcludeFile(string $filePath): bool
    {
        return $this->excludeRegistry->shouldExclude($filePath);
    }

    private function getPath(SplFileInfo|\SplFileInfo $file, string $basePath)
    {
        if ($file instanceof SplFileInfo) {
            return $file->getRelativePathname();
        }

        return \ltrim(\str_replace($basePath, '', $file->getRealPath()), '/');
    }
}
