<?php

declare(strict_types=1);

namespace Butschster\ContextGenerator\Config\Import;

use Butschster\ContextGenerator\Application\Logger\LoggerPrefix;
use Psr\Log\LoggerInterface;
use Spiral\Files\FilesInterface;
use Symfony\Component\Finder\Finder;

/**
 * Find files matching a glob pattern in the filesystem using Symfony Finder
 */
final readonly class WildcardPathFinder
{
    /**
     * File extensions recognized as configuration files
     */
    private const array CONFIG_EXTENSIONS = ['yaml', 'yml', 'json', 'php'];

    public function __construct(
        private FilesInterface $files,
        #[LoggerPrefix(prefix: 'wildcard-path-finder')]
        private ?LoggerInterface $logger = null,
    ) {}

    /**
     * Find files matching the given glob pattern
     *
     * @param string $pattern The glob pattern (can contain * and **)
     * @param string $basePath The base path for resolving relative patterns
     * @return array<string> List of absolute file paths that match the pattern
     */
    public function findMatchingPaths(string $pattern, string $basePath): array
    {
        $this->logger?->debug('Finding paths matching pattern', [
            'pattern' => $pattern,
            'basePath' => $basePath,
        ]);

        // If the pattern doesn't contain wildcards, just check if the file exists
        if (!PathMatcher::containsWildcard($pattern)) {
            $path = $this->resolvePath($pattern, $basePath);
            return $this->files->exists($path) ? [$path] : [];
        }

        // Determine the base directory to start scanning from
        $baseDir = $this->getBaseDirectoryFromPattern($pattern, $basePath);

        // If base directory doesn't exist, no files can match
        if (!\is_dir($baseDir)) {
            $this->logger?->debug('Base directory does not exist', ['baseDir' => $baseDir]);
            return [];
        }

        // Extract the pattern part after the base directory
        $patternSuffix = $this->getPatternSuffix($pattern, $baseDir, $basePath);

        try {
            // Create and configure Symfony Finder
            $finder = new Finder();
            $finder
                ->files()
                ->in($baseDir)
                ->name('/\.(' . \implode('|', self::CONFIG_EXTENSIONS) . ')$/')
                ->followLinks();

            // Apply the pattern as a path filter, but only if it's not just '*'
            if ($patternSuffix !== '*') {
                // Convert our glob pattern to a regex pattern compatible with Symfony's path() method
                $regexPattern = $this->convertGlobToFinderPathRegex($patternSuffix);
                $finder->path($regexPattern);
            }

            $matches = [];
            foreach ($finder as $file) {
                $matches[] = $file->getRealPath();
            }

            $this->logger?->debug('Found matching paths', ['count' => \count($matches), 'paths' => $matches]);
            return $matches;
        } catch (\InvalidArgumentException $e) {
            // Finder throws this if the directory doesn't exist
            $this->logger?->debug('Directory search failed', ['error' => $e->getMessage()]);
            return [];
        }
    }

    /**
     * Convert a glob pattern to a regex pattern compatible with Symfony Finder's path() method
     *
     * This method creates a regex pattern that will be compatible with Symfony Finder's
     * expectations for the path() method. It adds delimiters and ensures the pattern is
     * recognized as a regex pattern by Finder.
     */
    private function convertGlobToFinderPathRegex(string $pattern): string
    {
        // First, convert the glob pattern to a regex pattern using our PathMatcher
        $matcher = new PathMatcher($pattern);
        $regex = $matcher->getRegex();

        // Extract the pattern part between the delimiters (remove the ~^ at the start and $ at the end)
        $patternBody = \substr($regex, 2, -2);

        // Create a new regex pattern that will be recognized by Symfony Finder's path() method
        // We need to make sure it starts with a delimiter so it's recognized as a regex
        return '#' . $patternBody . '#';
    }

    /**
     * Get the pattern suffix (after the base directory)
     */
    private function getPatternSuffix(string $pattern, string $baseDir, string $basePath): string
    {
        $resolvedPattern = $this->resolvePath($pattern, $basePath);

        // If baseDir is part of the resolved pattern, extract the suffix
        $baseLength = \strlen($baseDir);
        if (\strncmp($resolvedPattern, $baseDir, $baseLength) === 0) {
            $suffix = \substr($resolvedPattern, $baseLength + 1);
            return $suffix ?: '*';
        }

        return $pattern;
    }

    /**
     * Get the base directory to start scanning from based on the pattern
     */
    private function getBaseDirectoryFromPattern(string $pattern, string $basePath): string
    {
        // Find the position of the first wildcard
        $firstWildcard = \strcspn($pattern, '*?[{');

        // Get the part of the pattern before the first wildcard
        $fixedPrefix = \substr($pattern, 0, $firstWildcard);

        // Get the directory part of the fixed prefix
        $baseDir = $fixedPrefix;
        if (\str_contains($fixedPrefix, '/')) {
            $baseDir = \dirname($fixedPrefix);
        }

        // If the base directory is empty, use the base path
        if ($baseDir === '' || $baseDir === '.') {
            return $basePath;
        }

        // Resolve the base directory against the base path
        return $this->resolvePath($baseDir, $basePath);
    }

    /**
     * Resolve a relative path to an absolute path
     */
    private function resolvePath(string $path, string $basePath): string
    {
        // If it's an absolute path, use it directly
        if (\str_starts_with($path, '/')) {
            return $path;
        }

        // Otherwise, resolve it relative to the base path
        return \rtrim($basePath, '/') . '/' . $path;
    }
}
