<?php

namespace App\Modules\Core\Commands;

use Illuminate\Console\Command;
use Illuminate\Contracts\Console\PromptsForMissingInput;
use Illuminate\Database\Migrations\MigrationCreator;
use Illuminate\Filesystem\Filesystem;
use Illuminate\Support\Str;

class MakeModule extends Command implements PromptsForMissingInput
{
    /**
     * Filesystem instance
     */
    protected Filesystem $files;

    /**
     * Module name
     */
    protected string $moduleName;

    /**
     * Module directory path
     */
    protected string $moduleDirectory;

    /**
     * The name and signature of the console command.
     *
     * @var string
     */
    protected $signature = 'make:module {module} {--plain : Create a plain module structure without default files}';

    /**
     * The console command description.
     *
     * @var string
     */
    protected $description = 'Create new module';

    /**
     * Prompt for missing input arguments using the returned questions.
     *
     * @return array<string, array<int, string>>
     */
    protected function promptForMissingArgumentsUsing(): array
    {
        return [
            'module' => ['Module name', 'News, Product, Order...'],
        ];
    }

    /**
     * Create a new command instance.
     *
     * @return void
     */
    public function __construct(Filesystem $files)
    {
        parent::__construct();

        $this->files = $files;
    }

    /**
     * Execute the console command.
     */
    public function handle(): void
    {
        /* @var string */
        $module = $this->argument('module');
        $this->moduleName = $module;
        $this->moduleDirectory = modules_path($module.'/');

        // Create module directory if not exists...
        $this->makeDirectory($this->moduleDirectory);

        // Make a module file...
        $this->makeModuleFile($this->moduleDirectory);

        // Repository...
        $this->makeDirectory($this->moduleDirectory.'Repositories');
        $this->makeRepository($this->moduleDirectory.'Repositories');

        if ($this->option('plain')) {
            return;
        }

        // Models...
        $this->makeDirectory($this->moduleDirectory.'Models');
        $this->makeModelFile($this->moduleDirectory.'Models');

        // Database...
        $this->makeDirectory($this->moduleDirectory.'Database');
        $this->makeDirectory($this->moduleDirectory.'Database/Migrations');
        $this->makeMigrationFile($this->moduleDirectory.'Database/Migrations');

        // Controller...
        $this->makeDirectory($this->moduleDirectory.'Controllers');
        $this->makeController($this->moduleDirectory.'Controllers');

        // Filament resource...
        // $this->makeDirectory($this->moduleDirectory.'Filament');
        // $this->makeDirectory($this->moduleDirectory.'Filament/Resources');
        // $this->makeFilamentResource($this->moduleDirectory.'Filament/Resources');
    }

    /**
     * Make module file
     */
    public function makeModuleFile(string $moduleDirectory): void
    {
        $creationStatus = $this->createFileFromStub(
            createFilePath: sprintf('%s/%sModule', $moduleDirectory, $this->moduleName),
            stubFile: __DIR__.'/stubs/make-module/module.stub',
            stubVariables: [
                'NAMESPACE' => sprintf('App\\Modules\\%s', $this->moduleName),
                'CLASS_NAME' => $this->moduleName.'Module',
            ]
        );

        $creationStatus
            ? $this->info("Module created: {$this->moduleName} created")
            : $this->info("Module: {$this->moduleName} already exits");
    }

    /**
     * Make model file
     */
    protected function makeModelFile(string $modelPath): void
    {
        $creationStatus = $this->createFileFromStub(
            createFilePath: sprintf('%s/%s', $modelPath, $this->moduleName),
            stubFile: __DIR__.'/stubs/make-module/model.stub',
            stubVariables: [
                'NAMESPACE' => sprintf('App\\Modules\\%s\\Models', $this->moduleName),
                'CLASS_NAME' => $this->moduleName,
            ]
        );

        $creationStatus
            ? $this->info("Model created: {$this->moduleName} created")
            : $this->info("Model: {$this->moduleName} already exits");
    }

    /**
     * Make migration file
     */
    protected function makeMigrationFile(string $migrationsPath): void
    {
        $migrationCreator = new MigrationCreator(
            files: new Filesystem,
            customStubPath: modules_path('Core/Commands/stubs/make-module/migration.stub')
        );

        $migrationPath = $migrationCreator->create(Str::lower('create_'.Str::snake(Str::plural($this->moduleName)).'_table'), $migrationsPath);
        $migrationName = Str::afterLast($migrationPath, '/');

        $this->info("Migration created: {$migrationName} created");
    }

    /**
     * Make controller file
     */
    protected function makeController(string $controllersPath): void
    {
        $creationStatus = $this->createFileFromStub(
            createFilePath: sprintf('%s/%sController', $controllersPath, $this->moduleName),
            stubFile: modules_path('Core/Commands/stubs/make-module/controller.stub'),
            stubVariables: [
                'NAMESPACE' => sprintf('App\\Modules\\%s\\Controllers', $this->moduleName),
                'CONTROLLER_NAME' => $this->moduleName.'Controller',
            ]
        );

        $creationStatus
            ? $this->info("Controller created: {$this->moduleName} created")
            : $this->info("Controller: {$this->moduleName} already exits");
    }

    /**
     * Make repository file
     */
    protected function makeRepository(string $path): void
    {
        $creationStatus = $this->createFileFromStub(
            createFilePath: sprintf('%s/%s', $path, $this->moduleName.'Repository'),
            stubFile: __DIR__.'/stubs/make-module/repository.stub',
            stubVariables: [
                'NAMESPACE' => sprintf('App\Modules\%s\Repositories', $this->moduleName),
                'MODULE_NAME' => ucfirst($this->moduleName),
                'MODEL_NAMESPACE' => sprintf('App\Modules\%s\Models\%s', $this->moduleName, $this->moduleName),
            ],
        );

        $creationStatus
            ? $this->info("Repository created: {$this->moduleName} created")
            : $this->info("Repository: {$this->moduleName} already exits");
    }

    /**
     * Make nova resource file
     */
    public function makeFilamentResource(string $path): void
    {
        // Base resource...
        $resourceCreateStatus = $this->createFileFromStub(
            createFilePath: sprintf('%s/%s', $path, $this->moduleName.'Resource'),
            stubFile: __DIR__.'/stubs/make-module/filament/base-resource.stub',
            stubVariables: [
                'NAMESPACE' => sprintf('App\Modules\%s\Filament\Resources', $this->moduleName),
                'MODEL_NAMESPACE' => sprintf('App\Modules\%s\Models\%s', $this->moduleName, $this->moduleName),
                'MODEL_NAME_SINGULAR' => Str::of($this->moduleName)->singular(),
                'MODEL_NAME_PLURAL' => Str::of($this->moduleName)->plural(),
            ],
        );

        $resourceCreateStatus
            ? $this->info("Filament resource created: {$this->moduleName} created")
            : $this->info("Filament resource: {$this->moduleName} already exits");

        $resourcePagesDir = $this->makeDirectory(sprintf('%s/%sResource', $path, $this->moduleName));

        // Create page...
        $resourceCreatePageStatus = $this->createFileFromStub(
            createFilePath: sprintf('%s/Create%s', $resourcePagesDir, $this->moduleName),
            stubFile: __DIR__.'/stubs/make-module/filament/pages/create-resource-page.stub',
            stubVariables: [
                'CREATE_RESOURCE_PAGE_NAMESPACE' => sprintf('App\Modules\%s\Filament\Resources\%sResource\Pages', $this->moduleName, $this->moduleName),
                'BASE_RESOURCE_NAMESPACE' => sprintf('App\Modules\%s\Filament\Resources\%sResource', $this->moduleName, $this->moduleName),
                'MODEL_NAME_SINGULAR' => Str::of($this->moduleName)->singular(),
            ],
        );

        $resourceCreatePageStatus
            ? $this->info("Filament create page resource created: {$this->moduleName} created")
            : $this->info("Filament create page resource: {$this->moduleName} already exits");

        // Edit page...
        $resourceEditPageStatus = $this->createFileFromStub(
            createFilePath: sprintf('%s/Edit%s', $resourcePagesDir, $this->moduleName),
            stubFile: __DIR__.'/stubs/make-module/filament/pages/edit-resource-page.stub',
            stubVariables: [
                'EDIT_RESOURCE_PAGE_NAMESPACE' => sprintf('App\Modules\%s\Filament\Resources\%sResource\Pages', $this->moduleName, $this->moduleName),
                'BASE_RESOURCE_NAMESPACE' => sprintf('App\Modules\%s\Filament\Resources\%sResource', $this->moduleName, $this->moduleName),
                'MODEL_NAME_SINGULAR' => Str::of($this->moduleName)->singular(),
            ],
        );

        $resourceEditPageStatus
            ? $this->info("Filament edit page resource created: {$this->moduleName} created")
            : $this->info("Filament edit page resource: {$this->moduleName} already exits");

        // List page...
        $resourceListPageStatus = $this->createFileFromStub(
            createFilePath: sprintf('%s/List%s', $resourcePagesDir, Str::of($this->moduleName)->plural()),
            stubFile: __DIR__.'/stubs/make-module/filament/pages/list-resource-page.stub',
            stubVariables: [
                'LIST_RESOURCE_PAGE_NAMESPACE' => sprintf('App\Modules\%s\Filament\Resources\%sResource\Pages', $this->moduleName, $this->moduleName),
                'BASE_RESOURCE_NAMESPACE' => sprintf('App\Modules\%s\Filament\Resources\%sResource', $this->moduleName, $this->moduleName),
                'MODEL_NAME_SINGULAR' => Str::of($this->moduleName)->singular(),
                'MODEL_NAME_PLURAL' => Str::of($this->moduleName)->plural(),
            ],
        );

        $resourceListPageStatus
            ? $this->info("Filament list page resource created: {$this->moduleName} created")
            : $this->info("Filament list page resource: {$this->moduleName} already exits");
    }

    /**
     * Build the directory for the class if necessary.
     */
    protected function makeDirectory(string $path): string
    {
        if (! $this->files->isDirectory($path)) {
            $this->files->makeDirectory($path, 0777, true, true);
        }

        return $path;
    }

    /**
     * Create a file from stub
     *
     * @param  array<string, string>  $stubVariables
     */
    protected function createFileFromStub(string $createFilePath, string $stubFile, array $stubVariables): int|bool
    {
        $contents = $this->getStubContents($stubFile, $stubVariables);

        return $this->files->missing($createFilePath)
            ? $this->files->put($createFilePath.'.php', $contents)
            : false;
    }

    /**
     * Replace the stub variables(key) with the desire value
     *
     * @param  array<string, string>  $stubVariables
     */
    protected function getStubContents(string $stub, array $stubVariables = []): string
    {
        $contents = (string) file_get_contents($stub);

        foreach ($stubVariables as $search => $replace) {
            $contents = str_replace('$'.$search.'$', $replace, $contents);
        }

        return $contents;
    }
}
