Your IP : 216.73.216.79


Current Path : /var/www/html/reddsis/app/Http/Controllers/Backend/
Upload File :
Current File : /var/www/html/reddsis/app/Http/Controllers/Backend/ArtisanGenController.php

<?php

namespace App\Http\Controllers\Backend;

use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Artisan;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\File;
use Illuminate\Support\Facades\Schema;
use Illuminate\Support\Str;

class ArtisanGenController extends Controller
{
    // Dashboard
    public function index()
    {
        return view('backend.module.artisanGen.index');
    }

    // Model Generator page
    public function model()
    {
        return view('backend.module.artisanGen.model', [
            'tables' => $this->getTables(),
        ]);
    }

    // CRUD Generator page
    public function crud()
    {
        return view('backend.module.artisanGen.crud', [
            'models' => $this->getExistingModels(),
            'flashSuccess' => session('artisangen_crud_success'),
        ]);
    }

    // AJAX: Preview model from DB table
    public function previewModel(Request $request)
    {
        try {
            $table = $request->input('table');
            $modelName = $request->input('model_name', $request->input('modelName')) ?: $this->tableToModel($table);
            $namespace = $this->stripModelNameFromNamespace(
                $this->normalizeNamespace($request->input('namespace', 'App\\Models\\Backend')),
                Str::studly($modelName)
            );
            $style = $request->input('style', 'attributes');

            if (!$table) {
                return response()->json(['error' => 'Table name is required'], 422);
            }

            if (!Schema::hasTable($table)) {
                return response()->json(['error' => "Table '{$table}' does not exist"], 422);
            }

            $rawNamespace = $this->normalizeNamespace($request->input('namespace', 'App\\Models\\Backend'));
            $studlyModel = Str::studly($modelName);
            $namespace = $this->stripModelNameFromNamespace($rawNamespace, $studlyModel);
            $wasCorrected = $namespace !== $rawNamespace;

            $code = $this->generateModelCode($table, $namespace, $modelName, $style);
            $path = $this->namespaceToPath($namespace) . "/{$modelName}.php";

            return response()->json([
                'files' => [
                    ['name' => $modelName . '.php', 'path' => $path, 'code' => $code],
                ],
                'namespace' => $namespace,
                'warning' => $wasCorrected ? "Model name '{$studlyModel}' was removed from the namespace automatically." : null,
            ]);
        } catch (\Throwable $e) {
            return response()->json(['error' => $e->getMessage()], 500);
        }
    }

    // AJAX: Generate model file
    public function generateModel(Request $request)
    {
        try {
            $table = $request->input('table');
            $modelName = $request->input('model_name', $request->input('modelName')) ?: $this->tableToModel($table);
            $rawNamespace = $this->normalizeNamespace($request->input('namespace', 'App\\Models\\Backend'));
            $studlyModel = Str::studly($modelName);
            $namespace = $this->stripModelNameFromNamespace($rawNamespace, $studlyModel);
            $wasCorrected = $namespace !== $rawNamespace;
            $style = $request->input('style', 'attributes');

            if (!$table || !Schema::hasTable($table)) {
                return response()->json(['error' => 'Valid table is required'], 422);
            }

            $code = $this->generateModelCode($table, $namespace, $modelName, $style);
            $path = base_path($this->namespaceToPath($namespace) . "/{$modelName}.php");

            File::makeDirectory(dirname($path), 0755, true, true);
            File::put($path, $code);

            return response()->json([
                'success' => true,
                'files' => [$this->namespaceToPath($namespace) . "/{$modelName}.php"],
                'namespace' => $namespace,
                'warning' => $wasCorrected ? "Model name '{$studlyModel}' was removed from the namespace automatically." : null,
            ]);
        } catch (\Throwable $e) {
            return response()->json(['error' => $e->getMessage()], 500);
        }
    }

    // Migration Generator page
    public function migration()
    {
        return view('backend.module.artisanGen.migration');
    }

    // AJAX: Preview migration
    public function previewMigration(Request $request)
    {
        try {
            $table = $this->normalizeTableName($request->input('table'));
            $fieldsString = $request->input('fields', '');
            $primaryKey = $request->input('primaryKey', $request->input('primary_key', 'id'));

            if (!$table) {
                return response()->json(['error' => 'Table name is required'], 422);
            }

            $code = $this->generateMigrationCode($table, $fieldsString, $primaryKey);
            $filename = date('Y_m_d_His') . "_create_{$table}_table.php";

            return response()->json([
                'files' => [
                    ['name' => $filename, 'path' => "database/migrations/{$filename}", 'code' => $code],
                ],
            ]);
        } catch (\Throwable $e) {
            return response()->json(['error' => $e->getMessage()], 500);
        }
    }

    // AJAX: Generate migration file
    public function generateMigration(Request $request)
    {
        try {
            $table = $this->normalizeTableName($request->input('table'));
            $fieldsString = $request->input('fields', '');
            $primaryKey = $request->input('primaryKey', $request->input('primary_key', 'id'));

            if (!$table) {
                return response()->json(['error' => 'Table name is required'], 422);
            }

            $code = $this->generateMigrationCode($table, $fieldsString, $primaryKey);
            $filename = date('Y_m_d_His') . "_create_{$table}_table.php";
            $path = base_path("database/migrations/{$filename}");

            File::put($path, $code);

            return response()->json([
                'success' => true,
                'files' => ["database/migrations/{$filename}"],
                'filename' => $filename,
            ]);
        } catch (\Throwable $e) {
            return response()->json(['error' => $e->getMessage()], 500);
        }
    }

    // AJAX: Run pending migrations
    public function runMigration(Request $request)
    {
        try {
            $filename = $request->input('filename');

            if ($filename && !File::exists(base_path("database/migrations/{$filename}"))) {
                return response()->json(['error' => "Migration file {$filename} not found"], 422);
            }

            $options = ['--force' => true];
            if ($filename) {
                $options['--path'] = "database/migrations/{$filename}";
            }

            Artisan::call('migrate', $options);
            $output = Artisan::output();

            return response()->json([
                'success' => true,
                'output' => $output,
            ]);
        } catch (\Throwable $e) {
            return response()->json(['error' => $e->getMessage()], 500);
        }
    }

    protected function generateMigrationCode($table, $fieldsString, $primaryKey)
    {
        $fields = $this->parseFields($fieldsString);
        $migrationFields = '';

        $hasCustomPrimaryKey = false;
        foreach ($fields as $f) {
            if ($f['name'] === $primaryKey) {
                $hasCustomPrimaryKey = true;
            }
            $migrationFields .= $this->generateColumnLine($f) . "\n            ";
        }

        if ($hasCustomPrimaryKey) {
            $idLine = '';
        } else {
            $idLine = $primaryKey === 'id' ? '$table->id();' : "\$table->id('{$primaryKey}');";
            $idLine .= "\n            ";
        }

        $stub = File::get(base_path('stubs/migration.stub'));
        return str_replace(
            ['{{tableName}}', '{{idLine}}', '{{fields}}'],
            [$table, $idLine, $migrationFields],
            $stub
        );
    }

    protected function generateColumnLine($field)
    {
        $name = $field['name'];
        $type = $field['type'];
        $params = $field['params'] ?? null;
        $modifiers = $field['modifiers'] ?? [];

        $specialNoName = ['timestamps', 'timestampsTz', 'softDeletes', 'softDeletesTz', 'rememberToken'];

        if (in_array($type, $specialNoName)) {
            return "\$table->{$type}();";
        }

        $args = [];

        if ($type === 'id') {
            return $name && $name !== 'id' ? "\$table->id('{$name}');" : "\$table->id();";
        }

        if ($type === 'foreignIdFor') {
            return "\$table->foreignIdFor({$params});";
        }

        if ($type === 'morphs' || $type === 'nullableMorphs' || $type === 'nullableUlidMorphs' || $type === 'nullableUuidMorphs' || $type === 'ulidMorphs' || $type === 'uuidMorphs') {
            return "\$table->{$type}('{$name}');";
        }

        $args[] = "'{$name}'";

        if ($params !== null) {
            $args[] = $params;
        }

        $line = "\$table->{$type}(" . implode(', ', $args) . ')';

        foreach ($modifiers as $modifier) {
            if (preg_match('/^(\w+)\((.*)\)$/s', $modifier, $m)) {
                $line .= "->{$m[1]}({$m[2]})";
            } else {
                $line .= "->{$modifier}()";
            }
        }

        return $line . ';';
    }

    protected function parseFields($fieldsString)
    {
        if (!$fieldsString) return [];

        $fieldStrings = $this->splitByCommaRespectingParentheses($fieldsString);
        $result = [];

        foreach ($fieldStrings as $field) {
            if (!$field) continue;

            $parts = $this->splitByColonRespectingQuotes($field);

            if (count($parts) < 2) {
                throw new \InvalidArgumentException("Invalid field definition: '{$field}'. Expected format: name:type or name:type:modifier or name:type(param):modifier");
            }

            $name = trim($parts[0]);
            $typePart = trim($parts[1]);
            $modifiers = array_slice($parts, 2);

            if (!preg_match('/^[A-Za-z_]\w*$/', $name)) {
                throw new \InvalidArgumentException("Invalid field name: '{$name}'");
            }

            $type = $typePart;
            $params = null;
            if (preg_match('/^(\w+)(?:\((.*)\))?$/s', $typePart, $matches)) {
                $type = $matches[1];
                $params = isset($matches[2]) && $matches[2] !== '' ? $matches[2] : null;
            }

            if (!$type || !preg_match('/^[A-Za-z_]\w*$/', $type)) {
                throw new \InvalidArgumentException("Invalid field type: '{$typePart}'");
            }

            $result[] = [
                'name' => $name,
                'type' => $type,
                'params' => $params,
                'modifiers' => $modifiers,
            ];
        }

        return $result;
    }

    protected function splitByCommaRespectingParentheses($string)
    {
        $items = [];
        $current = '';
        $depth = 0;

        for ($i = 0; $i < strlen($string); $i++) {
            $char = $string[$i];

            if ($char === '(') $depth++;
            elseif ($char === ')') $depth--;

            if ($char === ',' && $depth === 0) {
                $items[] = trim($current);
                $current = '';
            } else {
                $current .= $char;
            }
        }

        if (trim($current) !== '') {
            $items[] = trim($current);
        }

        return $items;
    }

    protected function splitByColonRespectingQuotes($string)
    {
        $parts = [];
        $current = '';
        $depth = 0;
        $inQuote = false;
        $quoteChar = null;

        for ($i = 0; $i < strlen($string); $i++) {
            $char = $string[$i];

            if (!$inQuote) {
                if ($char === '(') {
                    $depth++;
                    $current .= $char;
                } elseif ($char === ')') {
                    $depth--;
                    $current .= $char;
                } elseif ($char === '"' || $char === "'") {
                    $inQuote = true;
                    $quoteChar = $char;
                    $current .= $char;
                } elseif ($char === ':' && $depth === 0) {
                    $parts[] = trim($current);
                    $current = '';
                } else {
                    $current .= $char;
                }
            } else {
                $current .= $char;
                if ($char === $quoteChar && $string[$i - 1] !== '\\') {
                    $inQuote = false;
                    $quoteChar = null;
                }
            }
        }

        if (trim($current) !== '') {
            $parts[] = trim($current);
        }

        return $parts;
    }

    // AJAX: Preview CRUD from model
    public function previewCrud(Request $request)
    {
        try {
            $modelClass = $request->input('modelClass', $request->input('model_class'));

            if (!$modelClass) {
                return response()->json(['error' => 'Model class is required'], 422);
            }

            $options = [
                'controllerNamespace' => $request->input('controllerNamespace'),
                'requestNamespace' => $request->input('requestNamespace'),
                'viewPath' => $request->input('viewPath'),
            ];

            $files = $this->buildCrudFiles($modelClass, false, $options);
            return response()->json(['files' => $files]);
        } catch (\Throwable $e) {
            return response()->json(['error' => $e->getMessage()], 500);
        }
    }

    // AJAX: Generate CRUD files
    public function generateCrud(Request $request)
    {
        try {
            $modelClass = $request->input('modelClass', $request->input('model_class'));

            if (!$modelClass) {
                return response()->json(['error' => 'Model class is required'], 422);
            }

            $options = [
                'controllerNamespace' => $request->input('controllerNamespace'),
                'requestNamespace' => $request->input('requestNamespace'),
                'viewPath' => $request->input('viewPath'),
            ];

            $files = $this->buildCrudFiles($modelClass, false, $options);

            $paths = array_column($files, 'path');
            $route = $this->modelToRoute($modelClass);
            $controllerClass = class_basename($modelClass) . 'Controller';
            $viewFolder = Str::camel(class_basename($modelClass));

            // Write files after response is sent to avoid Vite hot-reload
            // race condition (Vite refreshes page when it detects new view files)
            app()->terminating(function () use ($files) {
                foreach ($files as $file) {
                    $path = base_path($file['path']);
                    File::makeDirectory(dirname($path), 0755, true, true);
                    File::put($path, $file['code']);
                }
            });

            // Store success in session so it survives Vite page reload
            session()->flash('artisangen_crud_success', [
                'files' => $paths,
                'route' => $route,
                'controllerClass' => $controllerClass,
                'viewFolder' => $viewFolder,
            ]);

            return response()->json([
                'success' => true,
                'files' => $paths,
                'route' => $route,
                'controllerClass' => $controllerClass,
                'viewFolder' => $viewFolder,
            ]);
        } catch (\Throwable $e) {
            return response()->json(['error' => $e->getMessage()], 500);
        }
    }

    // ──────────────────────────────────────────────
    // CORE: Generate Model Code from DB Table
    // ──────────────────────────────────────────────
    protected function generateModelCode($table, $namespace, $modelName, $style = 'attributes')
    {
        $columns = Schema::getColumns($table);
        $primaryKey = $this->getPrimaryKey($table);

        $fillable = [];
        $casts = [];
        $hidden = [];

        foreach ($columns as $col) {
            $name = $col['name'];
            $type = $col['type_name'] ?? 'string';

            if (in_array($name, [$primaryKey, 'created_at', 'updated_at'])) {
                continue;
            }

            $fillable[] = "'{$name}'";

            // Type casting
            if (in_array($type, ['int', 'integer', 'bigint', 'smallint', 'tinyint'])) {
                $casts[$name] = 'integer';
            } elseif ($type === 'boolean') {
                $casts[$name] = 'boolean';
            } elseif (in_array($type, ['datetime', 'timestamp'])) {
                $casts[$name] = 'datetime';
            } elseif ($type === 'date') {
                $casts[$name] = 'date';
            } elseif ($type === 'json') {
                $casts[$name] = 'json';
            }

            // Hidden fields detection
            if ($this->isHiddenField($name)) {
                $hidden[] = "'{$name}'";
            }
        }

        // Always cast audit & timestamp columns
        $columnNames = array_column($columns, 'name');
        if (in_array('created_by', $columnNames)) {
            $casts['created_by'] = 'integer';
        }
        if (in_array('updated_by', $columnNames)) {
            $casts['updated_by'] = 'integer';
        }
        if (in_array('created_at', $columnNames)) {
            $casts['created_at'] = 'datetime';
        }
        if (in_array('updated_at', $columnNames)) {
            $casts['updated_at'] = 'datetime';
        }
        if (in_array('deleted_at', $columnNames)) {
            $casts['deleted_at'] = 'datetime';
        }

        $itemIndent = $style === 'properties' ? "        " : "    ";
        $fillableStr = implode(",\n{$itemIndent}", $fillable);

        $castsLines = [];
        foreach ($casts as $k => $v) {
            $castsLines[] = "'{$k}' => '{$v}'";
        }
        $castsStr = implode(",\n            ", $castsLines);

        $hiddenAttr = '';
        $hiddenProp = '';
        if (!empty($hidden)) {
            $hiddenStr = implode(",\n{$itemIndent}", $hidden);
            $hiddenAttr = "#[Hidden([\n    {$hiddenStr}\n])]\n";
            $hiddenProp = "\n\n    protected \$hidden = [\n        {$hiddenStr}\n    ];";
        }

        $stubName = $style === 'properties' ? 'stubs/model-properties.stub' : 'stubs/model-attributes.stub';
        $stub = File::get(base_path($stubName));
        return str_replace(
            ['{{namespace}}', '{{modelName}}', '{{tableName}}', '{{primaryKey}}', '{{fillable}}', '{{casts}}', '{{hiddenAttr}}', '{{hiddenProp}}'],
            [$namespace, $modelName, $table, $primaryKey, $fillableStr, $castsStr, $hiddenAttr, $hiddenProp],
            $stub
        );
    }

    // ──────────────────────────────────────────────
    // CORE: Build CRUD Files from Model
    // ──────────────────────────────────────────────
    protected function buildCrudFiles($modelClass, $save, $options = [])
    {
        $modelName = class_basename($modelClass);
        $tableName = Str::snake(Str::pluralStudly($modelName));
        $primaryKey = $this->getPrimaryKey($tableName);
        $routePrefix = Str::kebab(Str::pluralStudly($modelName));
        $viewFolder = Str::camel($modelName);

        $controllerNamespace = $this->normalizeNamespace($options['controllerNamespace'] ?? 'App\\Http\\Controllers\\Backend');
        $requestNamespace = $this->normalizeNamespace($options['requestNamespace'] ?? 'App\\Http\\Requests\\Backend');
        $viewPath = trim($options['viewPath'] ?? 'backend.module._gen.' . $viewFolder, '\\/. ');
        $viewPath = preg_replace('#[/\\\\]+#', '.', $viewPath);

        $requestClass = $requestNamespace . '\\' . $modelName . 'Request';

        $columns = Schema::hasTable($tableName) ? Schema::getColumns($tableName) : [];
        $fields = [];
        foreach ($columns as $col) {
            if (in_array($col['name'], ['id', $primaryKey, 'created_at', 'updated_at', 'deleted_at', 'created_by', 'updated_by'])) {
                continue;
            }
            $fields[] = $col;
        }

        $files = [];

        // Controller
        $searchClauses = '';
        $storeFields = '';
        foreach ($fields as $f) {
            $searchClauses .= "\$sub->orWhere('{$f['name']}', 'like', \"%{\$search}%\");\n                ";
            $storeFields .= "'{$f['name']}' => \$request->{$f['name']},\n            ";
        }

        $controllerStub = File::get(base_path('stubs/controller.stub'));
        $controllerCode = str_replace(
            ['{{controllerNamespace}}', '{{modelName}}', '{{modelClass}}', '{{requestClass}}', '{{tableName}}', '{{routePrefix}}', '{{viewPath}}', '{{primaryKey}}', '{{searchClauses}}', '{{storeFields}}'],
            [$controllerNamespace, $modelName, $modelClass, $requestClass, $tableName, $routePrefix, $viewPath, $primaryKey, $searchClauses, $storeFields],
            $controllerStub
        );
        $files[] = ['name' => $modelName . 'Controller.php', 'path' => $this->namespaceToPath($controllerNamespace) . "/{$modelName}Controller.php", 'code' => $controllerCode];

        // Request
        $rules = '';
        foreach ($fields as $f) {
            $rules .= "'{$f['name']}' => 'nullable|string|max:255',\n            ";
        }
        $requestStub = File::get(base_path('stubs/request.stub'));
        $requestCode = str_replace(['{{requestNamespace}}', '{{modelName}}', '{{rules}}'], [$requestNamespace, $modelName, $rules], $requestStub);
        $files[] = ['name' => $modelName . 'Request.php', 'path' => $this->namespaceToPath($requestNamespace) . "/{$modelName}Request.php", 'code' => $requestCode];

        // Views
        $headers = '';
        $rows = '';
        $formFields = '';
        $formEditFields = '';
        foreach ($fields as $f) {
            $label = Str::headline($f['name']);
            $name = $f['name'];
            $type = $f['type_name'] ?? 'string';

            $headers .= "<th class=\"px-5 py-3 text-left\"><span class=\"text-xs font-medium uppercase tracking-wider text-gray-500\">{$label}</span></th>\n                                            ";
            $rows .= "<td class=\"px-5 py-4\"><span class=\"block text-sm text-gray-600\">{{ \$item->{$name} }}</span></td>\n                                            ";

            if (in_array($type, ['text', 'longtext', 'mediumtext'])) {
                $formFields .= $this->textareaField($label, $name, false);
                $formEditFields .= $this->textareaField($label, $name, true);
            } elseif (in_array($type, ['boolean', 'tinyint'])) {
                $formFields .= $this->checkboxField($label, $name, false);
                $formEditFields .= $this->checkboxField($label, $name, true);
            } else {
                $formFields .= $this->inputField($label, $name, false);
                $formEditFields .= $this->inputField($label, $name, true);
            }
        }

        $viewDir = 'resources/views/' . str_replace('.', '/', $viewPath);

        $indexStub = File::get(base_path('stubs/views/index.stub'));
        $files[] = ['name' => 'index.blade.php', 'path' => "{$viewDir}/index.blade.php", 'code' => str_replace(
            ['{{modelName}}', '{{routePrefix}}', '{{headers}}', '{{rows}}', '{{colspan}}'],
            [$modelName, $routePrefix, $headers, $rows, count($fields) + 2],
            $indexStub
        )];

        $createStub = File::get(base_path('stubs/views/create.stub'));
        $files[] = ['name' => 'create.blade.php', 'path' => "{$viewDir}/create.blade.php", 'code' => str_replace(
            ['{{modelName}}', '{{routePrefix}}', '{{formFields}}'],
            [$modelName, $routePrefix, $formFields],
            $createStub
        )];

        $editStub = File::get(base_path('stubs/views/edit.stub'));
        $files[] = ['name' => 'edit.blade.php', 'path' => "{$viewDir}/edit.blade.php", 'code' => str_replace(
            ['{{modelName}}', '{{routePrefix}}', '{{formEditFields}}'],
            [$modelName, $routePrefix, $formEditFields],
            $editStub
        )];

        // Setup guide
        $setupStub = File::get(base_path('stubs/views/setup.stub'));
        $files[] = ['name' => 'CRUD-SETUP.md', 'path' => "{$viewDir}/CRUD-SETUP.md", 'code' => str_replace(
            ['{{modelName}}', '{{routePrefix}}'],
            [$modelName, $routePrefix],
            $setupStub
        )];

        return $files;
    }

    // ──────────────────────────────────────────────
    // HELPERS
    // ──────────────────────────────────────────────
    protected function getTables()
    {
        $tables = DB::select('SHOW TABLES');
        $key = 'Tables_in_' . DB::getDatabaseName();
        $excluded = ['migrations', 'failed_jobs', 'cache', 'cache_locks', 'job_batches', 'jobs', 'sessions', 'password_reset_tokens', 'personal_access_tokens'];

        return collect($tables)
            ->pluck($key)
            ->reject(fn($t) => in_array($t, $excluded))
            ->values()
            ->toArray();
    }

    protected function getExistingModels()
    {
        $models = [];
        $dirs = [app_path('Models'), app_path('Models/Backend')];
        foreach ($dirs as $dir) {
            if (!File::isDirectory($dir)) continue;
            foreach (File::allFiles($dir) as $file) {
                $relative = $file->getRelativePathname();
                $ns = str_replace('/', '\\', dirname($relative));
                $base = $file->getFilenameWithoutExtension();
                $prefix = str_contains($dir, 'Backend') ? 'App\\Models\\Backend' : 'App\\Models';
                $models[] = $ns === '.' ? "{$prefix}\\{$base}" : "{$prefix}\\{$ns}\\{$base}";
            }
        }
        return array_unique($models);
    }

    protected function getPrimaryKey($table)
    {
        $cols = DB::select("SHOW KEYS FROM `{$table}` WHERE Key_name = 'PRIMARY'");
        return $cols[0]->Column_name ?? 'id';
    }

    protected function tableToModel($table)
    {
        $name = Str::singular($table);
        return Str::studly($name);
    }

    protected function modelToRoute($modelClass)
    {
        $name = class_basename($modelClass);
        return Str::kebab(Str::pluralStudly($name));
    }

    protected function isHiddenField($name)
    {
        $hiddenPatterns = ['password', 'remember_token', 'api_token', 'secret', 'pin', 'ssn', 'credit_card', 'cvv'];
        $lower = strtolower($name);

        foreach ($hiddenPatterns as $pattern) {
            if (str_contains($lower, $pattern)) {
                return true;
            }
        }

        return false;
    }

    protected function normalizeNamespace($namespace)
    {
        $namespace = trim($namespace, " \t\n\r\\/");
        $namespace = str_replace('/', '\\', $namespace);
        while (str_contains($namespace, '\\\\')) {
            $namespace = str_replace('\\\\', '\\', $namespace);
        }

        $parts = array_filter(explode('\\', $namespace));
        $parts = array_map(fn($part) => Str::studly($part), $parts);

        if (empty($parts) || $parts[0] !== 'App') {
            array_unshift($parts, 'App');
        } else {
            $parts[0] = 'App';
        }

        return implode('\\', $parts);
    }

    protected function stripModelNameFromNamespace(string $namespace, string $modelName): string
    {
        $parts = explode('\\', $namespace);
        $lastPart = end($parts);

        if ($lastPart === $modelName) {
            array_pop($parts);
        }

        return implode('\\', $parts);
    }

    protected function namespaceToPath($namespace)
    {
        return str_replace(['App\\', '\\'], ['app/', '/'], $namespace);
    }

    protected function normalizeTableName($table)
    {
        $table = trim($table);
        $table = Str::snake($table);
        $table = str_replace([' ', '-'], ['_', '_'], $table);
        $table = preg_replace('/[^a-z0-9_]/', '', strtolower($table));
        $table = preg_replace('/_+/', '_', $table);
        return trim($table, '_');
    }

    protected function inputField($label, $name, $isEdit)
    {
        $value = $isEdit ? "{{ old('{$name}', \$item->{$name}) }}" : "{{ old('{$name}') }}";
        return "<div class=\"mb-6\"><label class=\"mb-1.5 block text-sm font-medium\">{$label}</label><input type=\"text\" name=\"{$name}\" value=\"{$value}\" class=\"h-11 w-full rounded-lg border border-gray-300 px-4 text-sm\"></div>\n";
    }

    protected function textareaField($label, $name, $isEdit)
    {
        $value = $isEdit ? "{{ old('{$name}', \$item->{$name}) }}" : "{{ old('{$name}') }}";
        return "<div class=\"mb-6\"><label class=\"mb-1.5 block text-sm font-medium\">{$label}</label><textarea name=\"{$name}\" rows=\"4\" class=\"w-full rounded-lg border border-gray-300 px-4 py-2.5 text-sm\">{$value}</textarea></div>\n";
    }

    protected function checkboxField($label, $name, $isEdit)
    {
        $checked = $isEdit ? "{{ old('{$name}', \$item->{$name}) ? 'checked' : '' }}" : "{{ old('{$name}') ? 'checked' : '' }}";
        return "<div class=\"mb-6 flex items-center gap-3\"><label class=\"flex cursor-pointer items-center text-sm font-medium\"><input type=\"hidden\" name=\"{$name}\" value=\"0\"><input type=\"checkbox\" name=\"{$name}\" value=\"1\" {$checked} class=\"h-4 w-4\"><span class=\"ml-3\">{$label}</span></label></div>\n";
    }
}