<?php

namespace Wizard\MagicForger\Generator;

use Illuminate\Console\GeneratorCommand;
use Illuminate\Support\Facades\Schema;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Wizard\MagicForger\Replacer\Replacer;
use Wizard\MagicForger\Replacer\TableReplacer;

abstract class BaseGenerator extends GeneratorCommand
{
    use Replacer;
    use TableReplacer;

    protected $schema = null;

    protected $tables = null;

    protected $currentTable = null;

    protected static $cached_snippets = [];

    public function handle()
    {

        if (! $this->tableExists($this->getTableInput())) {
            $this->components->error('The table: "'.$this->getTableInput().'" does not exist in the database.');

            return false;
        }

        $this->setCurrentTable($this->getTableInput());
        $path = $this->getPath();
        $file = $this->getFile($path);
        $file = $this->apply_replacements($file);
        $file = $this->apply_inserts($file);
        $this->makeDirectory($path);
        $this->files->put($path, $this->sortImports($file));
        $this->format_file($path);
        $this->components->info(sprintf('%s [%s] created successfully.', $this->type, $path));
    }

    protected function promptForMissingArguments(InputInterface $input, OutputInterface $output): void
    {
        $prompted = false;
        if (is_null($input->getArgument('table'))) {
            $prompted = true;
            $table = null;
            while ($table === null) {
                $table = $this->components->askWithCompletion(
                    'What Table should we use?',
                    $this->possibleTables()
                );
            }
            $input->setArgument('table', $table);
        }

        parent::promptForMissingArguments($input, $output);

        if ($prompted) {
            $this->afterPromptingForMissingArguments($input, $output);
        }
    }

    protected function getArguments(): array
    {
        return [
            ['table', InputOption::VALUE_REQUIRED, 'The table to generate files for.'],
        ];
    }

    protected function promptForMissingArgumentsUsing(): array
    {
        return [];
    }

    protected function getOptions(): array
    {
        return [
            ['fresh', 'f', InputOption::VALUE_NONE, 'Start from the stub or use existing if possible.'],
        ];
    }

    protected function afterPromptingForMissingArguments(InputInterface $input, OutputInterface $output): void
    {
        // Additional logic after prompting goes here
    }

    protected function fileExists(string $path): bool
    {
        return $this->files->exists($path);
    }

    protected function getFile($name): string
    {
        if (! ($this->hasOption('fresh') && $this->option('fresh')) && $this->fileExists($name)) {
            return $this->files->get($name);
        }

        return $this->files->get($this->getStub());
    }

    protected function getTableInput(): string
    {
        return trim($this->argument('table'));
    }

    protected function tableExists(string $table_name): bool
    {
        return in_array($table_name, $this->getTables());
    }

    protected function possibleTables(): array
    {
        return $this->getTables();
    }

    protected function getTables(): array
    {
        if (is_null($this->tables)) {
            $this->tables = Schema::getTableListing(schema: config('database.connections.mariadb.database'), schemaQualified: false);
        }

        return $this->tables;
    }

    protected function getTable(string $table_name)
    {
        return $this->getSchema()->introspectTable($table_name);
    }

    /*
     * returns array of columns in the form of:
     *
    [
        "name" => "column_type"
    "type_name" => "bigint"
    "type" => "bigint(20) unsigned"
    "collation" => null
    "nullable" => true
    "default" => "NULL"
    "auto_increment" => false
    "comment" => null
    "generation" => null
    ]
    */
    protected static function getTableColumns(string $table_name)
    {
        return Schema::getColumns($table_name);
    }

    /*
     * returns array of foreign keys in the form of:
     *
     [
        "name" => "foreign_key_name"
        "columns" => [
            0 => "local_column_name"
        ]
        "foreign_schema" => "schema_name"
        "foreign_table" => "foreign_table_name"
        "foreign_columns" => [
            0 => "foreign_column_name"
        ]
        "on_update" => "restrict"
        "on_delete" => "restrict"
     ]
     *
     */
    protected static function getTableForeignKeys(string $table_name)
    {
        return Schema::getForeignKeys($table_name);
    }

    protected function getCurrentTable()
    {
        return $this->currentTable;
    }

    protected function setCurrentTable(string $table_name): void
    {
        $this->currentTable = $table_name;
    }

    protected function format_file(string $path): void
    {
        exec('./vendor/bin/pint '.escapeshellarg($path));
    }

    protected function getSnippet($snippet_name)
    {
        // Cache snippet contents to avoid re-reading files
        if (! isset(self::$cached_snippets[$snippet_name])) {
            self::$cached_snippets[$snippet_name] = $this->files->get(
                $this->resolveStubPath("/snippets/$snippet_name.stub"));
        }

        return self::$cached_snippets[$snippet_name];
    }
}
