<?php

declare(strict_types=1);

namespace Butschster\ContextGenerator\Config\Import;

use Butschster\ContextGenerator\Application\Logger\LoggerPrefix;
use Butschster\ContextGenerator\Config\Exception\ConfigLoaderException;
use Butschster\ContextGenerator\Config\Import\Merger\ConfigMergerProviderInterface;
use Butschster\ContextGenerator\Config\Import\PathPrefixer\DocumentOutputPathPrefixer;
use Butschster\ContextGenerator\Config\Import\PathPrefixer\SourcePathPrefixer;
use Butschster\ContextGenerator\Config\Import\Source\Config\SourceConfigFactory;
use Butschster\ContextGenerator\Config\Import\Source\Config\SourceConfigInterface;
use Butschster\ContextGenerator\Config\Import\Source\Exception\ImportSourceException;
use Butschster\ContextGenerator\Config\Import\Source\ImportedConfig;
use Butschster\ContextGenerator\Config\Import\Source\ImportSourceProvider;
use Butschster\ContextGenerator\Config\Import\Source\Local\LocalSourceConfig;
use Butschster\ContextGenerator\Config\Import\Source\Url\UrlSourceConfig;
use Butschster\ContextGenerator\DirectoriesInterface;
use Psr\Log\LoggerInterface;
use Spiral\Files\FilesInterface;

/**
 * Resolves import directives in configuration files
 */
final readonly class ImportResolver
{
    public function __construct(
        private DirectoriesInterface $dirs,
        FilesInterface $files,
        private ImportSourceProvider $sourceProvider,
        private WildcardPathFinder $pathFinder,
        private ConfigMergerProviderInterface $configMergerProvider,
        private DocumentOutputPathPrefixer $documentPrefixer,
        private SourcePathPrefixer $sourcePrefixer,
        private SourceConfigFactory $sourceConfigFactory = new SourceConfigFactory(),
        private CircularImportDetectorInterface $detector = new CircularImportDetector(),
        #[LoggerPrefix(prefix: 'import-resolver')]
        private ?LoggerInterface $logger = null,
    ) {}

    /**
     * Process imports in a configuration
     * @throws \Throwable
     */
    public function resolveImports(array $config, string $basePath, array &$parsedImports = []): ResolvedConfig
    {
        // If no imports, return the original config
        if (empty($config['import'])) {
            return new ResolvedConfig(config: $config);
        }

        $imports = $config['import'];
        if (!\is_array($imports)) {
            throw new ConfigLoaderException('The "import" property must be an array');
        }

        // Process each import
        /** @var ImportedConfig[] $importedConfigs */
        $importedConfigs = [];
        foreach ($imports as $importConfig) {
            // Create source configuration from the raw import config
            $sourceConfig = $this->sourceConfigFactory->createFromArray($importConfig, $basePath);

            // Handle wildcard paths (only for local sources)
            if ($sourceConfig instanceof LocalSourceConfig && $sourceConfig->hasWildcard()) {
                $this->processWildcardImport(
                    $sourceConfig,
                    $basePath,
                    $parsedImports,
                    $importedConfigs,
                );
                continue;
            }

            // For local imports, check if already processed
            if ($sourceConfig instanceof LocalSourceConfig) {
                $absolutePath = $sourceConfig->getAbsolutePath();
                if (\in_array($absolutePath, $parsedImports, true)) {
                    $this->logger?->debug('Skipping already processed import', [
                        'path' => $sourceConfig->getPath(),
                        'type' => $sourceConfig->getType(),
                    ]);
                    continue;
                }
            }

            // Process a single import
            $this->processSingleImport(
                $sourceConfig,
                $basePath,
                $parsedImports,
                $importedConfigs,
            );
        }

        unset($config['import']);

        // Merge all configurations using the merger provider
        return new ResolvedConfig(
            config: $this->configMergerProvider->mergeConfigurations($config, ...$importedConfigs),
            imports: \array_map(
                static fn(ImportedConfig $config) => $config->sourceConfig,
                $importedConfigs,
            ),
        );
    }

    /**
     * Process a wildcard import pattern
     */
    private function processWildcardImport(
        LocalSourceConfig $sourceConfig,
        string $basePath,
        array &$parsedImports,
        array &$importedConfigs,
    ): void {
        // Find all files that match the pattern
        $matchingPaths = $this->pathFinder->findMatchingPaths($sourceConfig->getPath(), $basePath);

        if (empty($matchingPaths)) {
            $this->logger?->warning('No files match the wildcard pattern', [
                'pattern' => $sourceConfig->getPath(),
                'basePath' => $basePath,
            ]);
            return;
        }

        $this->logger?->debug('Found files matching wildcard pattern', [
            'pattern' => $sourceConfig->getPath(),
            'count' => \count($matchingPaths),
            'paths' => $matchingPaths,
        ]);

        // Process each matching file
        foreach ($matchingPaths as $matchingPath) {
            // Skip if already processed
            if (\in_array($matchingPath, $parsedImports, true)) {
                $this->logger?->debug('Skipping already processed wildcard match', [
                    'path' => $matchingPath,
                ]);
                continue;
            }

            $rootPathStr = (string) $this->dirs->getRootPath();

            // Create a local source config for this match
            $localConfig = new LocalSourceConfig(
                path: \ltrim(\str_replace($rootPathStr, '', $matchingPath), '/'),
                absolutePath: $matchingPath,
                hasWildcard: false,
                pathPrefix: $sourceConfig->getPathPrefix(),
                selectiveDocuments: $sourceConfig->getSelectiveDocuments(),
            );

            // Process it using the standard import logic
            $this->processSingleImport(
                $localConfig,
                \dirname($matchingPath), // Base path is the directory of the matched file
                $parsedImports,
                $importedConfigs,
            );
        }
    }

    /**
     * Process a single non-wildcard import
     */
    private function processSingleImport(
        SourceConfigInterface $sourceConfig,
        string $basePath,
        array &$parsedImports,
        array &$importedConfigs,
    ): void {
        // For circular dependency detection
        $importId = match (true) {
            $sourceConfig instanceof LocalSourceConfig => $sourceConfig->getAbsolutePath(),
            $sourceConfig instanceof UrlSourceConfig => $sourceConfig->getPath(),
        };

        // Check for circular imports
        $this->detector->beginProcessing($importId);

        try {
            // Find an appropriate import source using the provider
            $importSource = $this->sourceProvider->findSourceForConfig($sourceConfig);

            if (!$importSource) {
                throw new ImportSourceException(
                    \sprintf(
                        'No import source found for type "%s" and path "%s"',
                        $sourceConfig->getType(),
                        $sourceConfig->getPath(),
                    ),
                );
            }

            // Load the configuration using the appropriate source
            $importedConfig = $importSource->load($sourceConfig);

            // For external sources, we need to resolve the base path differently
            $importBasePath = match ($sourceConfig->getType()) {
                'local' => $sourceConfig->getConfigDirectory(),
                'url' => '', // URL paths are absolute
                default => $basePath,
            };

            // Recursively process nested imports
            $importedConfig = $this->resolveImports(
                $importedConfig,
                $importBasePath,
                $parsedImports,
            );

            // Apply source path prefix for local imports
            if ($sourceConfig instanceof LocalSourceConfig) {
                if ($sourceConfig->getPathPrefix() !== null) {
                    $importedConfig = new ResolvedConfig(
                        config: $this->documentPrefixer->applyPrefix(
                            $importedConfig->config,
                            $sourceConfig->getPathPrefix(),
                        ),
                        imports: $importedConfig->imports,
                    );
                }
                $importedConfig = new ResolvedConfig(
                    config: $this->sourcePrefixer->applyPrefix(
                        $importedConfig->config,
                        $sourceConfig->getConfigDirectory(),
                    ),
                    imports: $importedConfig->imports,
                );

                // Mark as processed for local sources
                $parsedImports[] = $sourceConfig->getAbsolutePath();
            }

            // Store for later merging
            $importedConfigs[] = new ImportedConfig(
                sourceConfig: $sourceConfig,
                config: $importedConfig->config,
                path: $sourceConfig->getPath(),
                isLocal: $sourceConfig instanceof LocalSourceConfig,
            );

            $this->logger?->debug('Successfully processed import', [
                'type' => $sourceConfig->getType(),
            ]);
        } catch (\Throwable $e) {
            $this->logger?->error('Failed to process import', [
                'type' => $sourceConfig->getType(),
                'error' => $e->getMessage(),
            ]);
            // Ignore the error if the import is broken
        } finally {
            // Always end processing to maintain stack integrity
            $this->detector->endProcessing($importId);
        }
    }
}
