<?php

namespace Vanguard\Plugins\Console\Commands;

use Illuminate\Contracts\Filesystem\FileNotFoundException;
use Symfony\Component\Process\Exception\ProcessFailedException;
use Symfony\Component\Process\Process;

class GeneratePluginCommand extends PluginCommand
{
    /**
     * The console command name.
     *
     * @var string
     */
    protected $name = 'vanguard:make-plugin';

    /**
     * The console command description.
     *
     * @var string
     */
    protected $description = 'Scaffold all the necessary files for the new plugin.';

    /**
     * Execute the console command.
     *
     * @throws FileNotFoundException
     */
    public function handle()
    {
        if ($this->pluginExists($this->pluginPath())) {
            $this->error('Plugin already exists!');

            return;
        }

        $this->warn('Generating plugin folder structure...');
        $this->generateFolderStructure();
        $this->generateFiles();
        $this->info('Plugin folder structure generated successfully.');

        $this->warn('Installing plugin as a local composer dependency...');
        $this->updateApplicationComposerFile();
        $this->installPlugin();
        $this->info('Plugin installed successfully.');
    }

    /**
     * Generate the plugin folder structure.
     */
    private function generateFolderStructure(): void
    {
        $pluginPath = $this->pluginPath();

        foreach ($this->getFolders() as $folder) {
            $this->makeDirectory($pluginPath."/{$folder}");
        }
    }

    /**
     * Get the list of folders to create.
     */
    protected function getFolders(): array
    {
        return [
            'database/factories',
            'database/migrations',
            'database/seeds',
            'resources/views',
            'routes',
            'src/Http/Controllers/Api',
            'src/Http/Controllers/Web',
            'src/Http/Requests',
            'tests',
        ];
    }

    /**
     * Generate necessary plugin files.
     *
     * @throws \Illuminate\Contracts\Filesystem\FileNotFoundException
     */
    private function generateFiles(): void
    {
        $pluginPath = $this->pluginPath();

        foreach ($this->getFiles() as $stubPath => $filePath) {
            $this->makeDirectory($pluginPath."/{$filePath}");

            $stub = $this->files->get(__DIR__.'/stubs/'.$stubPath);
            $stub = $this->replacePlaceholders($stub);

            $this->files->put($pluginPath."/{$filePath}", $stub);
        }
    }

    /**
     * Get the list of files that should be created for the plugin.
     */
    protected function getFiles(): array
    {
        return [
            'routes/web.stub' => 'routes/web.php',
            'routes/api.stub' => 'routes/api.php',
            'views/index.stub' => 'resources/views/index.blade.php',
            'config.stub' => 'config/config.php',
            'composer.stub' => 'composer.json',
            'assets/js/app.stub' => 'resources/assets/js/app.js',
            'assets/sass/app.stub' => 'resources/assets/sass/app.scss',
            'webpack.stub' => 'webpack.mix.js',
            'package.stub' => 'package.json',
            'unit-test.stub' => 'tests/Unit/ExampleTest.php',
            'feature-test.stub' => 'tests/Feature/FeatureTest.php',
            'gitignore.stub' => '.gitignore',
            'plugin.stub' => 'src/'.$this->studlyName().'.php',
            'controller.stub' => 'src/Http/Controllers/Web/'.$this->studlyName().'Controller.php',
        ];
    }

    /**
     * Replace placeholders within the stub files.
     *
     * @return mixed
     */
    private function replacePlaceholders(string $stub)
    {
        return str_replace(
            [
                '$ROOT_NAMESPACE$',
                '$PLUGIN_NAMESPACE$',
                '$VENDOR$',
                '$AUTHOR_NAME$',
                '$AUTHOR_EMAIL$',
                '$SNAKE_NAME$',
                '$STUDLY_NAME$',
            ],
            [
                $this->rootNamespace(),
                $this->pluginNamespace(),
                config('plugins.composer.vendor'),
                config('plugins.composer.author.name'),
                config('plugins.composer.author.email'),
                $this->snakeName(),
                $this->studlyName(),
            ],
            $stub
        );
    }

    /**
     * Update the main application composer file to include the
     * newly generated plugin.
     *
     * @throws \Illuminate\Contracts\Filesystem\FileNotFoundException
     */
    private function updateApplicationComposerFile(): void
    {
        $composer = json_decode($this->files->get(base_path('composer.json')), true);

        $composer['repositories'][] = [
            'type' => 'path',
            'url' => './plugins/'.$this->studlyName(),
        ];

        $this->files->put(
            base_path('composer.json'),
            json_encode($composer, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES)
        );
    }

    /**
     * Install the plugin via composer.
     */
    private function installPlugin(): void
    {
        $pluginFullName = sprintf('%s/%s', config('plugins.composer.vendor'), $this->snakeName());

        $command = Process::fromShellCommandline("composer require {$pluginFullName} \"*\"");

        $command->setWorkingDirectory(base_path());

        $command->run();

        if (! $command->isSuccessful()) {
            throw new ProcessFailedException($command);
        }
    }
}
