<?php

declare(strict_types=1);

namespace app\vendor\apexl\io\src\Io\modules\system\commands;

use apexl\Io\interfaces\HasServicesInterface;
use apexl\Io\modules\menu\interfaces\registersMenuItems;
use apexl\Io\modules\user\interfaces\registersPermissions;
use app\vendor\apexl\io\src\Io\interfaces\HasHooksInterface;
use app\vendor\apexl\io\src\Io\modules\system\dtos\CreateModuleDto;
use app\vendor\apexl\io\src\Io\modules\system\exceptions\ModuleFolderExistsException;
use app\vendor\apexl\io\src\Io\modules\system\traits\HasMustache;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;

#[AsCommand(name: 'modules:create', description: 'Create a new IO Module')]
class ModuleCreateCommand extends Command
{
    use HasMustache;

    private string $moduleName;
    private string $moduleFolder;
    private CreateModuleDto $dto;
    private string $namespace;

    protected function mustacheTemplateFolder(): string
    {
        return sprintf('%s/vendor/apexl/io/src/Io/modules/system/templates', BASE_DIR);
    }

    protected function configure(): void
    {
        $this->addArgument(
            'name',
            InputArgument::REQUIRED,
            'The name of the module to create',
        );
    }

    protected function execute(InputInterface $input, OutputInterface $output): int
    {

        try {
            $this->dto = CreateModuleDto::fromUserInput(
                $input,
                $output,
                $this->getHelper('question')
            );

            $this->moduleName = $input->getArgument('name');
            $this->moduleFolder = $this->moduleFolder();
            $this->namespace = $this->namespace();

            $this->createModuleFolder();
            $this->createModuleFile();

            $this->createMetaFiles();

            if ($this->dto->routes) {
                $this->createRoutesFile();
            }

            if ($this->dto->services) {
                $this->createDummyInterface();
                $this->createDummyService();
            }

            if ($this->dto->hooks) {
                $this->createDummyHook();
            }

        } catch (ModuleFolderExistsException $e) {
            $output->writeln($e->getMessage());

            return 1;
        }

        return 0;
    }

    private function moduleFolder(): string
    {
        return sprintf('%s/src/Io/modules/%s', $this->moduleRepositoryFolder(), $this->moduleName);
    }

    private function moduleRepositoryFolder(): string
    {
        return sprintf('%s/vendor/apexl/%s', BASE_DIR, $this->repositoryName());
    }

    private function repositoryName(): string
    {
        return sprintf('io-%s', $this->moduleName);
    }

    private function namespace(): string
    {
        return sprintf('apexl\\Io\\modules\\%s', $this->moduleName);
    }

    /**
     * @throws ModuleFolderExistsException
     */
    private function createModuleFolder(): void
    {
        $this->assertModuleFolderDoesNotExist();

        mkdir($this->moduleFolder, 0775, true);
    }

    /**
     * @throws ModuleFolderExistsException
     */
    private function assertModuleFolderDoesNotExist(): void
    {
        if (file_exists($this->moduleFolder)) {
            throw new ModuleFolderExistsException(sprintf('Module %s already exists', $this->moduleName));
        }
    }

    private function createModuleFile(): void
    {
        $filename = sprintf('%s/%sModule.php', $this->moduleFolder, $this->moduleName);
        $this->mustacheRenderToFile('module', $filename, $this->moduleContext());
    }

    private function moduleContext(): array
    {
        return [
            'moduleName' => $this->moduleName,
            'namespace' => $this->namespace,
            'implements' => $this->moduleImplements(),
            'routes' => $this->dto->routes,
            'services' => $this->dto->services,
            'hooks' => $this->dto->hooks,
            'menus' => $this->dto->menus,
            'permissions' => $this->dto->permissions,
        ];
    }

    private function moduleImplements(): string
    {
        return implode(
            ', ',
            array_map(
                fn($cls) => sprintf('\\%s', $cls),
                array_filter([
                    $this->dto->services ? HasServicesInterface::class : '',
                    $this->dto->hooks ? HasHooksInterface::class : '',
                    $this->dto->menus ? RegistersMenuItems::class : '',
                    $this->dto->permissions ? RegistersPermissions::class : '',
                ])
            )
        );
    }

    private function createMetaFiles(): void
    {
        $this->createGitignoreFile();
        $this->createReleaseItFile();
        $this->createComposerFile();
        $this->createPackageJsonFile();
    }

    private function createGitignoreFile(): void
    {
        $this->mustacheRenderToFile(
            '.gitignore',
            sprintf('%s/.gitignore', $this->moduleRepositoryFolder()),
        );
    }

    private function createReleaseItFile(): void
    {
        $this->mustacheRenderToFile(
            '.release-it.json',
            sprintf('%s/.release-it.json', $this->moduleRepositoryFolder()),
        );
    }

    private function createComposerFile(): void
    {
        $this->mustacheRenderToFile(
            'composer.json',
            sprintf('%s/composer.json', $this->moduleRepositoryFolder()),
            [
                'name' => $this->repositoryName(),
            ]
        );
    }

    private function createPackageJsonFile(): void
    {
        $this->mustacheRenderToFile(
            'package.json',
            sprintf('%s/package.json', $this->moduleRepositoryFolder()),
            [
                'name' => $this->repositoryName(),
            ]
        );
    }

    private function createRoutesFile(): void
    {
        mkdir(sprintf('%s/routes', $this->moduleFolder), 0775, true);
        $this->mustacheRenderToFile(
            'routes',
            sprintf('%s/routes/routes.php', $this->moduleFolder),
        );
    }

    private function createDummyInterface(): void
    {
        $this->createDummyFile('MyInterface', 'Interface', 'interface');
    }

    private function createDummyFile(
        string $file,
        ?string $folder = null,
        ?string $template = null,
    ): void {
        $folder = $folder ?? $file;
        $template = $template ?? strtolower($file);
        mkdir(sprintf('%s/%s', $this->moduleFolder, $folder), 0775, true);

        $this->mustacheRenderToFile(
            $template,
            sprintf('%s/%s/%s.php', $this->moduleFolder, $folder, $file),
            [
                'namespace' => $this->namespace,
            ]
        );
    }

    private function createDummyService(): void
    {
        $this->createDummyFile('Service');
    }

    private function createDummyHook(): void
    {
        $this->createDummyFile('Hook');
    }
}
