<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Bundle\FrameworkBundle\Command;

use Symfony\Component\Config\Definition\ConfigurationInterface;
use Symfony\Component\Console\Exception\LogicException;
use Symfony\Component\Console\Helper\Table;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\StyleInterface;
use Symfony\Component\DependencyInjection\Extension\ConfigurationExtensionInterface;
use Symfony\Component\DependencyInjection\Extension\ExtensionInterface;

/**
 * A console command for dumping available configuration reference.
 *
 * @author Kevin Bond <kevinbond@gmail.com>
 * @author Wouter J <waldio.webdesign@gmail.com>
 * @author Grégoire Pineau <lyrixx@lyrixx.info>
 */
abstract class AbstractConfigCommand extends ContainerDebugCommand
{
    /**
     * @return void
     */
    protected function listBundles(OutputInterface|StyleInterface $output)
    {
        $title = 'Available registered bundles with their extension alias if available';
        $headers = ['Bundle name', 'Extension alias'];
        $rows = [];

        $bundles = $this->getApplication()->getKernel()->getBundles();
        usort($bundles, fn ($bundleA, $bundleB) => strcmp($bundleA->getName(), $bundleB->getName()));

        foreach ($bundles as $bundle) {
            $extension = $bundle->getContainerExtension();
            $rows[] = [$bundle->getName(), $extension ? $extension->getAlias() : ''];
        }

        if ($output instanceof StyleInterface) {
            $output->title($title);
            $output->table($headers, $rows);
        } else {
            $output->writeln($title);
            $table = new Table($output);
            $table->setHeaders($headers)->setRows($rows)->render();
        }
    }

    protected function listNonBundleExtensions(OutputInterface|StyleInterface $output): void
    {
        $title = 'Available registered non-bundle extension aliases';
        $headers = ['Extension alias'];
        $rows = [];

        $kernel = $this->getApplication()->getKernel();

        $bundleExtensions = [];
        foreach ($kernel->getBundles() as $bundle) {
            if ($extension = $bundle->getContainerExtension()) {
                $bundleExtensions[$extension::class] = true;
            }
        }

        $extensions = $this->getContainerBuilder($kernel)->getExtensions();

        foreach ($extensions as $alias => $extension) {
            if (isset($bundleExtensions[$extension::class])) {
                continue;
            }
            $rows[] = [$alias];
        }

        if (!$rows) {
            return;
        }

        if ($output instanceof StyleInterface) {
            $output->title($title);
            $output->table($headers, $rows);
        } else {
            $output->writeln($title);
            $table = new Table($output);
            $table->setHeaders($headers)->setRows($rows)->render();
        }
    }

    protected function findExtension(string $name): ExtensionInterface
    {
        $bundles = $this->initializeBundles();
        $minScore = \INF;

        $kernel = $this->getApplication()->getKernel();
        if ($kernel instanceof ExtensionInterface && ($kernel instanceof ConfigurationInterface || $kernel instanceof ConfigurationExtensionInterface)) {
            if ($name === $kernel->getAlias()) {
                return $kernel;
            }

            if ($kernel->getAlias()) {
                $distance = levenshtein($name, $kernel->getAlias());

                if ($distance < $minScore) {
                    $guess = $kernel->getAlias();
                    $minScore = $distance;
                }
            }
        }

        foreach ($bundles as $bundle) {
            if ($name === $bundle->getName()) {
                if (!$bundle->getContainerExtension()) {
                    throw new \LogicException(sprintf('Bundle "%s" does not have a container extension.', $name));
                }

                return $bundle->getContainerExtension();
            }

            $distance = levenshtein($name, $bundle->getName());

            if ($distance < $minScore) {
                $guess = $bundle->getName();
                $minScore = $distance;
            }
        }

        $container = $this->getContainerBuilder($kernel);

        if ($container->hasExtension($name)) {
            return $container->getExtension($name);
        }

        foreach ($container->getExtensions() as $extension) {
            $distance = levenshtein($name, $extension->getAlias());

            if ($distance < $minScore) {
                $guess = $extension->getAlias();
                $minScore = $distance;
            }
        }

        if (!str_ends_with($name, 'Bundle')) {
            $message = sprintf('No extensions with configuration available for "%s".', $name);
        } else {
            $message = sprintf('No extension with alias "%s" is enabled.', $name);
        }

        if (isset($guess) && $minScore < 3) {
            $message .= sprintf("\n\nDid you mean \"%s\"?", $guess);
        }

        throw new LogicException($message);
    }

    /**
     * @return void
     */
    public function validateConfiguration(ExtensionInterface $extension, mixed $configuration)
    {
        if (!$configuration) {
            throw new \LogicException(sprintf('The extension with alias "%s" does not have its getConfiguration() method setup.', $extension->getAlias()));
        }

        if (!$configuration instanceof ConfigurationInterface) {
            throw new \LogicException(sprintf('Configuration class "%s" should implement ConfigurationInterface in order to be dumpable.', get_debug_type($configuration)));
        }
    }

    private function initializeBundles(): array
    {
        // Re-build bundle manually to initialize DI extensions that can be extended by other bundles in their build() method
        // as this method is not called when the container is loaded from the cache.
        $kernel = $this->getApplication()->getKernel();
        $container = $this->getContainerBuilder($kernel);
        $bundles = $kernel->getBundles();
        foreach ($bundles as $bundle) {
            if ($extension = $bundle->getContainerExtension()) {
                $container->registerExtension($extension);
            }
        }

        foreach ($bundles as $bundle) {
            $bundle->build($container);
        }

        return $bundles;
    }
}
