<?php

namespace Wizard\MagicForger\Generator\Model;

use Symfony\Component\Console\Attribute\AsCommand;
use Wizard\MagicForger\Generator\BaseGenerator;
use Wizard\MagicForger\Helpers\RelationshipNavigator;
use Illuminate\Support\Str;

#[AsCommand(name: 'mf:model')]
class ModelGenerator extends BaseGenerator
{
    /**
     * The name and signature of the console command.
     *
     * @var string
     */
    protected $name = 'mf:model';

    /**
     * The console command description.
     *
     * @var string
     */
    protected $description = 'Generates the Model File for a table.';

    /**
     * The type of class being generated.
     *
     * @var string
     */
    protected $type = 'Model';

    protected static $cached_snippets = [];

    /**
     * Execute the console command.
     *
     * @return mixed
     */
    public function handle()
    {
        // Delegate to parent handler (includes replacements and insertions)
        return parent::handle();
    }

    /**
     * Get the stub file for the generator.
     *
     * @return string
     */
    protected function getStub()
    {
        if (! is_null(RelationshipNavigator::isPivot($this->getCurrentTable()))) {
            return $this->resolveStubPath('/stubs/model.pivot.stub');
        }

        return $this->resolveStubPath('/stubs/model.stub');
    }

    /**
     * Resolve the fully-qualified path to the stub.
     *
     * @param  string  $stub
     * @return string
     */
    protected function resolveStubPath($stub)
    {
        return is_file($customPath = $this->laravel->basePath(trim($stub, '/')))
            ? $customPath
            : __DIR__.$stub;
    }

    protected function getClassName($name)
    {
        return $this->model_name($name);
    }

    /**
     * Get the stub file for the generator.
     *
     * @return string
     */
    protected function getPath($name = null)
    {
        return str_replace(['App\\', '\\'], ['app/', '/'], $this->getModelNamespace().'/'.$this->model_name($this->getTableInput()).'.php');
    }

		protected function gatherRelations() {
			$relations = RelationshipNavigator::getRelations($this->getCurrentTable());

			return $relations;
			
		}

		protected function renderFilters() {
        $insert = '';
        foreach ($this->get_columns() as $column) {
            if (in_array($column['name'], $this->columns_to_ignore)) {
                continue;
            }
						$snippet = $this->getSnippet('filter');
						$tableName = $this->getCurrentTable();
						$value = 'value'; // TODO: this should be determined based on column type
						$columnName = $column['name'];
						$columnDisplay = Str::headline($columnName);

						// Replace placeholders with actual values
        		$string = str_replace(
        		    ['{{value}}', '{{columnDisplay}}', '{{tableName}}', '{{columnName}}'],
        		    [$value, $columnDisplay, $tableName, $columnName],
        		    $snippet
        		);
            $insert .= sprintf("%s", $string);
        }

				return $insert;
		}

		protected function renderRelations($relations) {
			$renders = [
				'belongsTo' => [],
				'hasMany' => [],
				'belongsToMany' => [],
			];
        // Render belongsTo relations
        foreach (($relations['belongsTo'] ?? []) as $relation) {
            $renders['belongsTo'][] = $this->renderBelongsTo($relation);
        }

        // Render hasMany relations
        foreach (($relations['hasMany'] ?? []) as $relation) {
            $renders['hasMany'][] = $this->renderHasMany($relation);
        }

			// Render belongsToMany (many-to-many) via hasManyThrough pivot relations
			foreach (($relations['hasManyThrough'] ?? []) as $relation) {
				$renders['belongsToMany'][] = $this->renderBelongsToMany($relation);
			}
			return $renders;
		}

    protected function renderBelongsTo($relationship)
    {
        $snippet = $this->getSnippet('belongs_to_relation');
        $relationName = Str::singular($relationship['table']);
        $relatedModel = $this->getClassName($relationship['table']);
        $columnName = $relationship['column'];

        // Replace placeholders with actual values
        $string = str_replace(
            ['{{relationName}}', '{{relatedModel}}', '{{columnName}}'],
            [$relationName, $relatedModel, $columnName],
            $snippet
        );

        return $string;
    }

    /**
     * Render a hasMany relation.
     *
     * @param array $relationship
     * @return string
     */
    protected function renderHasMany($relationship)
    {
        $snippet = $this->getSnippet('has_many_relation');
        // Method name uses camel case for plural relation
        $relationName = Str::camel($relationship['table']);
        $relatedModel = $this->getClassName($relationship['table']);
        $columnName = $relationship['column'];

        // Replace placeholders with actual values
        $string = str_replace(
            ['{{relationName}}', '{{relatedModel}}', '{{columnName}}'],
            [$relationName, $relatedModel, $columnName],
            $snippet
        );

        return $string;
    }

    protected function renderBelongsToMany($relationship)
    {
        $snippet = $this->getSnippet('belongs_to_many_relation');
        $relationName = $relationship['table'];
        $relatedModel = $this->getClassName($relationship['table']);
        $pivotTable = $relationship['through']['table'];
        $foreignPivotKey = $relationship['through']['external_column'];
        $relatedPivotKey = $relationship['through']['internal_column'];

        // Replace placeholders with actual values
        $string = str_replace(
            ['{{relationName}}', '{{relatedModel}}', '{{pivotTable}}', '{{foreignPivotKey}}', '{{relatedPivotKey}}'],
            [$relationName, $relatedModel, $pivotTable, $foreignPivotKey, $relatedPivotKey],
            $snippet
        );

        return $string;
    }

    /**
     * Get available insertions including model relationships.
     *
     * @return array
     */
    public function get_available_inserts(): array
    {
        // Merge parent insertions (attributes, fillable, etc.)
        $inserts = parent::get_available_inserts();

        // Gather and render relationships for this model
        $relations = $this->gatherRelations();
        $rendered = $this->renderRelations($relations);
				$filters = $this->renderFilters();

        // Build code blocks for each relation type
        $belongs = !empty($rendered['belongsTo']) ? implode("\n    ", $rendered['belongsTo']) : '';
        $hasMany = !empty($rendered['hasMany']) ? implode("\n    ", $rendered['hasMany']) : '';
        $belongsMany = !empty($rendered['belongsToMany']) ? implode("\n    ", $rendered['belongsToMany']) : '';

				// Default relations are based on the belongsTo relationship
				$default_relations = implode(", \n",  array_map(function ($rel) {return '\'' . Str::singular($rel['table']) . '\''; }, $relations['belongsTo']));

        // Assign to stub placeholders
        $inserts['# {{ belongs_to_relationships }}'] = $belongs;
        $inserts['# {{ has_many_relationships }}'] = $hasMany;
        $inserts['# {{ has_many_through_relationships }}'] = $belongsMany;
				$inserts['# {{ defaultRelationsInsertPoint }}'] = $default_relations;
				$inserts['# {{ defaultFiltersInsertPoint }}'] = $filters;

        return $inserts;
    }
}
