<?php

declare(strict_types=1);

/*
 * This file is part of the TYPO3 CMS project.
 *
 * It is free software; you can redistribute it and/or modify it under
 * the terms of the GNU General Public License, either version 2
 * of the License, or any later version.
 *
 * For the full copyright and license information, please read the
 * LICENSE.txt file that was distributed with this source code.
 *
 * The TYPO3 project - inspiring people to share!
 */

namespace TYPO3\CMS\Install;

use Psr\Container\ContainerInterface;
use TYPO3\CMS\Core\Cache\CacheManager;
use TYPO3\CMS\Core\Configuration\ConfigurationManager;
use TYPO3\CMS\Core\Console\CommandRegistry;
use TYPO3\CMS\Core\Context\Context;
use TYPO3\CMS\Core\Crypto\HashService;
use TYPO3\CMS\Core\Crypto\PasswordHashing\PasswordHashFactory;
use TYPO3\CMS\Core\Crypto\Random;
use TYPO3\CMS\Core\DependencyInjection\ContainerBuilder;
use TYPO3\CMS\Core\FormProtection\FormProtectionFactory;
use TYPO3\CMS\Core\Html\SanitizerBuilderFactory;
use TYPO3\CMS\Core\Http\MiddlewareDispatcher;
use TYPO3\CMS\Core\Imaging\IconFactory;
use TYPO3\CMS\Core\Imaging\IconRegistry;
use TYPO3\CMS\Core\Localization\LanguageServiceFactory;
use TYPO3\CMS\Core\Localization\Locales;
use TYPO3\CMS\Core\Localization\TranslationDomainResolver;
use TYPO3\CMS\Core\Log\LogManager;
use TYPO3\CMS\Core\Mail\Mailer;
use TYPO3\CMS\Core\Mail\TemplatedEmailFactory;
use TYPO3\CMS\Core\Middleware\NormalizedParamsAttribute as NormalizedParamsMiddleware;
use TYPO3\CMS\Core\Middleware\ResponsePropagation as ResponsePropagationMiddleware;
use TYPO3\CMS\Core\Package\AbstractServiceProvider;
use TYPO3\CMS\Core\Package\FailsafePackageManager;
use TYPO3\CMS\Core\Package\PackageManager;
use TYPO3\CMS\Core\Page\ResourceHashCollection;
use TYPO3\CMS\Core\PasswordPolicy\Generator\PasswordGenerator;
use TYPO3\CMS\Core\PasswordPolicy\PasswordService;
use TYPO3\CMS\Core\Routing\BackendEntryPointResolver;
use TYPO3\CMS\Core\Security\ContentSecurityPolicy\DirectiveHashCollection;
use TYPO3\CMS\Core\Service\SilentConfigurationUpgradeService;
use TYPO3\CMS\Core\SystemResource\Publishing\SystemResourcePublisherInterface;
use TYPO3\CMS\Core\SystemResource\SystemResourceFactory;
use TYPO3\CMS\Core\TypoScript\AST\CommentAwareAstBuilder;
use TYPO3\CMS\Core\TypoScript\AST\Traverser\AstTraverser;
use TYPO3\CMS\Core\TypoScript\Tokenizer\LosslessTokenizer;
use TYPO3\CMS\Core\ViewHelpers\IconViewHelper;
use TYPO3\CMS\Core\ViewHelpers\NormalizedUrlViewHelper;
use TYPO3\CMS\Fluid\ViewHelpers\Be\InfoboxViewHelper;
use TYPO3\CMS\Fluid\ViewHelpers\Sanitize\HtmlViewHelper;
use TYPO3\CMS\Install\Service\LateBootService;
use TYPO3\CMS\Install\Service\SessionService;
use TYPO3\CMS\Install\Service\WebServerConfigurationFileService;

/**
 * @internal
 */
class ServiceProvider extends AbstractServiceProvider
{
    protected static function getPackagePath(): string
    {
        return __DIR__ . '/../';
    }

    protected static function getPackageName(): string
    {
        return 'typo3/cms-install';
    }

    public function getFactories(): array
    {
        return [
            Authentication\AuthenticationService::class => self::getAuthenticationService(...),
            Http\Application::class => self::getApplication(...),
            Http\NotFoundRequestHandler::class => self::getNotFoundRequestHandler(...),
            Factory\ImportMapFactory::class => self::getImportMapFactory(...),
            Service\ClearCacheService::class => self::getClearCacheService(...),
            Service\CoreUpdateService::class => self::getCoreUpdateService(...),
            Service\CoreVersionService::class => self::getCoreVersionService(...),
            Service\LateBootService::class => self::getLateBootService(...),
            Service\SilentTemplateFileUpgradeService::class => self::getSilentTemplateFileUpgradeService(...),
            Service\WebServerConfigurationFileService::class => self::getWebServerConfigurationFileService(...),
            Service\SessionService::class => self::getSessionService(...),
            Middleware\Installer::class => self::getInstallerMiddleware(...),
            Middleware\Maintenance::class => self::getMaintenanceMiddleware(...),
            Middleware\AssetPublishing::class => self::getAssetPublishing(...),
            Middleware\JavaScriptLanguageDomainProvider::class => self::getJavaScriptLanguageDomainProvider(...),
            Controller\EnvironmentController::class => self::getEnvironmentController(...),
            Controller\IconController::class => self::getIconController(...),
            Controller\LayoutController::class => self::getLayoutController(...),
            Controller\LoginController::class => self::getLoginController(...),
            Controller\MaintenanceController::class => self::getMaintenanceController(...),
            Controller\SettingsController::class => self::getSettingsController(...),
            Controller\UpgradeController::class => self::getUpgradeController(...),
            Command\PasswordSetCommand::class => self::getPasswordGenerateCommand(...),
            Command\SetupCommand::class => self::getSetupCommand(...),
            Command\SetupDefaultBackendUserGroupsCommand::class => self::getSetupDefaultBackendUserGroupsCommand(...),
            IconViewHelper::class => self::getIconViewHelper(...),
            HtmlViewHelper::class => self::getHtmlViewHelper(...),
            InfoboxViewHelper::class => self::getInfoboxViewHelper(...),
            NormalizedUrlViewHelper::class => self::getNormalizedUrlViewHelper(...),
            PasswordGenerator::class => self::getPasswordGenerator(...),
            Random::class => self::getRandom(...),
        ];
    }

    public function getExtensions(): array
    {
        return [
            'backend.routes' => [ static::class, 'configureBackendRoutes' ],
            'backend.modules' => [ static::class, 'configureBackendModules' ],
            'icons' => [ static::class, 'configureIcons' ],
            CommandRegistry::class => self::configureCommands(...),
            DirectiveHashCollection::class => self::provideFallbackDirectiveHashCollection(...),
            Service\SetupService::class => self::provideSetupService(...),
        ];
    }

    public static function getAuthenticationService(ContainerInterface $container): Authentication\AuthenticationService
    {
        return new Authentication\AuthenticationService(
            $container->get(Mailer::class),
            $container->get(TemplatedEmailFactory::class)
        );
    }

    public static function getApplication(ContainerInterface $container): Http\Application
    {
        $requestHandler = $container->get(Http\NotFoundRequestHandler::class);
        $dispatcher = new MiddlewareDispatcher($requestHandler, [], $container);

        // Stack of middlewares, executed LIFO
        $dispatcher->lazy(ResponsePropagationMiddleware::class);
        $dispatcher->lazy(Middleware\Installer::class);
        $dispatcher->add($container->get(Middleware\Maintenance::class));
        $dispatcher->add($container->get(Middleware\AssetPublishing::class));
        $dispatcher->add($container->get(Middleware\JavaScriptLanguageDomainProvider::class));
        $dispatcher->lazy(NormalizedParamsMiddleware::class);

        return self::new($container, Http\Application::class, [
            $dispatcher,
            $container->get(Context::class),
        ]);
    }

    public static function getNotFoundRequestHandler(ContainerInterface $container): Http\NotFoundRequestHandler
    {
        return new Http\NotFoundRequestHandler();
    }

    public static function getImportMapFactory(ContainerInterface $container): Factory\ImportMapFactory
    {
        return new Factory\ImportMapFactory(
            $container->get(FailsafePackageManager::class),
            $container->get(HashService::class),
        );
    }

    public static function getClearCacheService(ContainerInterface $container): Service\ClearCacheService
    {
        return new Service\ClearCacheService(
            $container->get(Service\LateBootService::class),
            $container->get('cache.di')
        );
    }

    public static function getCoreUpdateService(ContainerInterface $container): Service\CoreUpdateService
    {
        return new Service\CoreUpdateService(
            $container->get(Service\CoreVersionService::class)
        );
    }

    public static function getCoreVersionService(ContainerInterface $container): Service\CoreVersionService
    {
        return new Service\CoreVersionService();
    }

    public static function getLateBootService(ContainerInterface $container): Service\LateBootService
    {
        return new Service\LateBootService(
            $container->get(ContainerBuilder::class),
            $container
        );
    }

    public static function getSilentTemplateFileUpgradeService(ContainerInterface $container): Service\SilentTemplateFileUpgradeService
    {
        return new Service\SilentTemplateFileUpgradeService(
            $container->get(WebServerConfigurationFileService::class)
        );
    }

    public static function getWebServerConfigurationFileService(ContainerInterface $container): Service\WebServerConfigurationFileService
    {
        return self::new($container, Service\WebServerConfigurationFileService::class);
    }

    public static function getSessionService(ContainerInterface $container): Service\SessionService
    {
        return new Service\SessionService(
            $container->get(Service\LateBootService::class),
            $container->get(LogManager::class)->getLogger(Service\SessionService::class)
        );
    }

    public static function provideSetupService(ContainerInterface $container, ?Service\SetupService $setupService): Service\SetupService
    {
        if ($setupService === null) {
            $lateBootService = $container->get(Service\LateBootService::class);
            $setupService = $lateBootService->getContainer(true)->get(Service\SetupService::class);
            $lateBootService->unsetInternalContainerInstance();
        }
        return $setupService;
    }

    public static function getInstallerMiddleware(ContainerInterface $container): Middleware\Installer
    {
        return new Middleware\Installer(
            $container->get(Service\LateBootService::class),
            $container->get(FormProtectionFactory::class),
            $container->get(SessionService::class),
        );
    }

    public static function getMaintenanceMiddleware(ContainerInterface $container): Middleware\Maintenance
    {
        return new Middleware\Maintenance(
            $container->get(FailsafePackageManager::class),
            $container->get(ConfigurationManager::class),
            $container->get(PasswordHashFactory::class),
            $container,
            $container->get(FormProtectionFactory::class),
            $container->get(SessionService::class),
        );
    }

    public static function getJavaScriptLanguageDomainProvider(ContainerInterface $container): Middleware\JavaScriptLanguageDomainProvider
    {
        return new Middleware\JavaScriptLanguageDomainProvider(
            $container->get(LanguageServiceFactory::class),
            $container->get(TranslationDomainResolver::class),
        );
    }

    public static function getAssetPublishing(ContainerInterface $container): Middleware\AssetPublishing
    {
        return new Middleware\AssetPublishing(
            $container->get(PackageManager::class),
            $container->get(SystemResourcePublisherInterface::class),
        );
    }

    public static function getEnvironmentController(ContainerInterface $container): Controller\EnvironmentController
    {
        return new Controller\EnvironmentController(
            $container->get(Service\LateBootService::class),
            $container->get(FormProtectionFactory::class),
            $container->get(Mailer::class),
            $container->get(TemplatedEmailFactory::class),
        );
    }

    public static function getIconController(ContainerInterface $container): Controller\IconController
    {
        return new Controller\IconController(
            $container->get(IconFactory::class)
        );
    }

    public static function getLayoutController(ContainerInterface $container): Controller\LayoutController
    {
        return new Controller\LayoutController(
            $container->get(SilentConfigurationUpgradeService::class),
            $container->get(Service\SilentTemplateFileUpgradeService::class),
            $container->get(BackendEntryPointResolver::class),
            $container->get(Factory\ImportMapFactory::class),
            $container->get(HashService::class),
            $container->get(IconRegistry::class),
            $container->get(DirectiveHashCollection::class),
        );
    }

    public static function getLoginController(ContainerInterface $container): Controller\LoginController
    {
        return new Controller\LoginController(
            $container->get(FormProtectionFactory::class),
            $container->get(ConfigurationManager::class),
        );
    }

    public static function getMaintenanceController(ContainerInterface $container): Controller\MaintenanceController
    {
        return new Controller\MaintenanceController(
            $container->get(Service\LateBootService::class),
            $container->get(Service\ClearCacheService::class),
            $container->get(ConfigurationManager::class),
            $container->get(PasswordHashFactory::class),
            $container->get(Locales::class),
            $container->get(LanguageServiceFactory::class),
            $container->get(FormProtectionFactory::class),
        );
    }

    public static function getSettingsController(ContainerInterface $container): Controller\SettingsController
    {
        return new Controller\SettingsController(
            $container->get(Service\LateBootService::class),
            $container->get(PackageManager::class),
            $container->get(LanguageServiceFactory::class),
            $container->get(CommentAwareAstBuilder::class),
            $container->get(LosslessTokenizer::class),
            $container->get(AstTraverser::class),
            $container->get(FormProtectionFactory::class),
            $container->get(ConfigurationManager::class),
            $container->get(PasswordService::class),
        );
    }

    public static function getUpgradeController(ContainerInterface $container): Controller\UpgradeController
    {
        return new Controller\UpgradeController(
            $container->get(PackageManager::class),
            $container->get(Service\LateBootService::class),
            $container->get(FormProtectionFactory::class),
        );
    }

    public static function getSetupCommand(ContainerInterface $container): Command\SetupCommand
    {
        return new Command\SetupCommand(
            'setup',
            $container->get(Service\SetupService::class),
            $container->get(ConfigurationManager::class),
            $container->get(LateBootService::class),
            $container->get(FailsafePackageManager::class),
        );
    }

    public function getSetupDefaultBackendUserGroupsCommand(ContainerInterface $container): Command\SetupDefaultBackendUserGroupsCommand
    {
        return new Command\SetupDefaultBackendUserGroupsCommand(
            'setup:begroups:default',
            $container->get(Service\SetupService::class),
        );
    }

    public function getPasswordGenerateCommand(ContainerInterface $container): Command\PasswordSetCommand
    {
        return new Command\PasswordSetCommand(
            'install:password:set',
            $container->get(PasswordHashFactory::class),
            $container->get(ConfigurationManager::class),
            $container->get(LanguageServiceFactory::class),
            $container->get(PasswordService::class),
        );
    }

    public static function getPermissionsCheck(ContainerInterface $container): Database\PermissionsCheck
    {
        return new Database\PermissionsCheck();
    }

    public static function getIconViewHelper(ContainerInterface $container): IconViewHelper
    {
        return self::new($container, IconViewHelper::class, [
            $container->get(IconFactory::class),
        ]);
    }

    public static function getHtmlViewHelper(ContainerInterface $container): HtmlViewHelper
    {
        return self::new($container, HtmlViewHelper::class, [
            new SanitizerBuilderFactory(),
        ]);
    }

    public static function getInfoboxViewHelper(ContainerInterface $container): InfoboxViewHelper
    {
        return self::new($container, InfoboxViewHelper::class, [
            $container->get(IconFactory::class),
        ]);
    }

    public static function getNormalizedUrlViewHelper(ContainerInterface $container): NormalizedUrlViewHelper
    {
        return self::new($container, NormalizedUrlViewHelper::class, [
            $container->get(SystemResourceFactory::class),
            $container->get(SystemResourcePublisherInterface::class),
        ]);
    }

    public static function getPasswordGenerator(ContainerInterface $container): PasswordGenerator
    {
        return self::new($container, PasswordGenerator::class, [$container->get(Random::class)]);
    }

    public static function provideFallbackDirectiveHashCollection(
        ContainerInterface $container,
        ?DirectiveHashCollection $directiveHashCollection = null,
    ): DirectiveHashCollection {
        return $directiveHashCollection ?? self::new($container, DirectiveHashCollection::class, [
            new ResourceHashCollection(
                $container->get(LogManager::class)->getLogger(ResourceHashCollection::class),
                $container->get(SystemResourceFactory::class),
                $container->get(CacheManager::class)->getCache('assets'),
            ),
        ]);
    }

    public static function getRandom(ContainerInterface $container): Random
    {
        return self::new($container, Random::class);
    }

    public static function configureCommands(ContainerInterface $container, CommandRegistry $commandRegistry): CommandRegistry
    {
        $commandRegistry->addLazyCommand(
            'setup',
            Command\SetupCommand::class,
            'Setup TYPO3 via CLI.'
        );
        $commandRegistry->addLazyCommand(
            'setup:begroups:default',
            Command\SetupDefaultBackendUserGroupsCommand::class,
            'Setup default backend user groups "Editor" and "Advanced Editor".'
        );
        $commandRegistry->addLazyCommand(
            'install:password:set',
            Command\PasswordSetCommand::class,
            'Set or generate a new install tool password'
        );
        return $commandRegistry;
    }
}
