<?php

namespace App\Console\Commands;

use Illuminate\Console\Command;
use Illuminate\Support\Facades\File;
use Illuminate\Support\Str;
use Symfony\Component\Process\Process;

class MakeCrudCommand extends Command
{
  /**
   * The name and signature of the console command.
   *
   * @var string
   */
  protected $signature = 'make:crud {migration_file} {--label=} {--rollback} {--prefix_url=} {--format} {--icon=} {--namespace=}';

  /**
   * The console command description.
   *
   * @var string
   */
  protected $description = 'Generate a controller and React views, and append resource routes. Use --rollback to reverse.';

  protected $stubsPath;
  protected $validationDictionary;
  protected $iconDictionary;
  protected $configEntry;

  public function __construct()
  {
    parent::__construct();
    $this->stubsPath = __DIR__ . '/../../../stubs/';
  }

  /**
   * Execute the console command.
   *
   * @return int
   */
  public function handle()
  {
    $this->validationDictionary = require $this->stubsPath . 'Databases/validation.php';
    $this->iconDictionary = require $this->stubsPath . 'Databases/icon.php';

    $migrationFile = $this->argument('migration_file');
    $migrationPath = database_path('migrations/' . $migrationFile . '.php');

    if (!File::exists($migrationPath)) {
      $this->error("Migration file not found at: {$migrationPath}");
      return 1;
    }

    // Load config for this migration
    $this->configEntry = collect(config('crudgenerator.data'))->firstWhere('migration', $migrationFile);
    $isGenerate = isset($this->configEntry['is_generate']) ? $this->configEntry['is_generate'] : true;
    if (!$isGenerate) {
      $this->info("Skipping generation for '{$migrationFile}'.");
      return 0;
    }

    $content = File::get($migrationPath);
    if (!preg_match("/Schema::create\('(?<table>\w+)'/", $content, $matches)) {
      $this->error("Could not find table name in migration file.");
      return 1;
    }
    $tableName = $matches['table'];

    // If 'model' is not defined in config, try to guess it
    $modelName = $this->configEntry['model'] ?? Str::studly(Str::singular($tableName));

    $modelNameCamel = Str::camel($modelName);
    $modelNamePlural = Str::plural($modelName);
    $modelNameCamelPlural = Str::camel($modelNamePlural);

    $subNamespace = $this->option('namespace') ?? "Admin";
    $adminPath = $this->option('namespace') ?? "Admin";
    $subNamespace = str_replace("/", "\\", $subNamespace);

    $pageLabel = $this->configEntry['label'] ?? ($this->option('label') ?? Str::headline($modelName));

    $prefixUrl = $this->configEntry['prefix_url'] ?? $this->option('prefix_url');
    $basePageUrl = Str::kebab($tableName);
    if ($prefixUrl) {
      $pageUrl = Str::of($prefixUrl)->trim('/') . '/' . $basePageUrl;
    } else {
      $pageUrl = "admin/" . $basePageUrl;
    }

    // Placeholders
    $placeholders = [
      '{{ SubNamespace }}' => $subNamespace,
      '{{ AdminPath }}' => $adminPath,
      '{{ ModelName }}' => $modelName,
      '{{ modelNameCamel }}' => $modelNameCamel,
      '{{ modelNamePlural }}' => $modelNamePlural,
      '{{ modelNameCamelPlural }}' => $modelNameCamelPlural,
      '{{ tableName }}' => $tableName,
      '{{ pageLabel }}' => $pageLabel,
      '{{ pageUrl }}' => $pageUrl,
    ];

    if ($this->option('rollback')) {
      $this->info("Starting rollback for '{$tableName}'...");
      $this->rollbackController($placeholders);
      $this->rollbackReactViews($placeholders);
      $this->rollbackRoute($placeholders);
      $this->info("Rollback complete.");
      return 0;
    }

    // Analyze columns
    // We still parse migration for backup or for validation rule guessing
    preg_match_all("/->(?<type>\w+)\(\s*['\"](?<column>\w+)['\"].*?\)(?<options>.*?);/", $content, $columnMatches, PREG_SET_ORDER);

    $placeholders = array_merge($placeholders, $this->generateCodeContent($columnMatches, $tableName, $modelName));

    $this->createController($placeholders);
    $this->createReactView('Index', $placeholders);
    $this->createReactView('Create', $placeholders);
    $this->createReactView('Edit', $placeholders);
    $this->createReactView('MainForm', $placeholders);
    $this->createReactView('Show', $placeholders);

    $this->appendRouteToWebPhp($placeholders);

    $isReactPage = $this->configEntry['is_react_page'] ?? true;
    if ($isReactPage) {
      $this->appendSidebarMenuEntry($placeholders);
    }

    if ($this->option('format')) {
      $this->runFormatter($modelName, $placeholders);
    }

    return 0;
  }

  protected function generateCodeContent($columnMatches, $tableName, $modelName)
  {
    $validationRules = "";
    $formFieldsData = "";
    $useEffectHooks = "";

    $tableHeaderEntries = [];
    // Add numbering
    $tableHeaderEntries[] = [
      'label' => '#',
      'value' => 'id',
      'type' => 'numbering',
    ];

    $formProperties = [];
    $extraOptions = []; // Store additional options found in form_layout

    // Build Index Columns from Config or Fallback to Migration
    $configColumns = $this->configEntry['columns'] ?? [];
    $defaultColumns = $this->configEntry['defaults'] ?? [];

    // If Config Columns exist, iterate them
    if (!empty($configColumns)) {
      foreach ($configColumns as $field => $def) {
        $entry = ['value' => $field];
        // Merge defaults with config definition
        $entry = array_merge($entry, $def);

        // Smart defaults if missing
        if (!isset($entry['label']))
          $entry['label'] = Str::headline($field);
        if (Str::endsWith($field, '_at') && !isset($entry['type'])) {
          $entry['type'] = 'date';
          // Add default classname for dates if not set
          if (!isset($entry['className']))
            $entry['className'] = 'text-[13px] w-[10%] md:w-[100px] !whitespace-pre';
        }

        if (in_array($field, array_keys($defaultColumns))) {
          $entry['prefix'] = Str::snake($modelName) . '.' . $field;
        }
        if (Str::contains($field, 'date')) {
          $entry['type'] = 'date';
        } else if ($field == "status") {
          $entry['type'] = 'status';
        }

        $tableHeaderEntries[] = $entry;
      }
    } else {
      // Fallback: Use migration parsing
      foreach ($columnMatches as $match) {
        $column = $match['column'];
        if (in_array($column, ['id', 'created_at', 'updated_at', 'user_id', 'deleted_at']))
          continue;

        $header = ['label' => Str::headline($column), 'value' => $column];

        if (Str::endsWith($column, '_id')) {
          $relationName = Str::camel(Str::beforeLast($column, '_id'));
          $header['value'] = "{$relationName}.name";
          $header['isSearchable'] = true;
          $header['className'] = "text-[13px] w-[10%] md:w-[100px] !whitespace-pre";
        }
        if ($column == "status") {
          $header['type'] = 'status';
        }
        if (in_array($column, array_keys($defaultColumns))) {
          $header['prefix'] = Str::snake($modelName) . '.' . $column;
        }

        $tableHeaderEntries[] = $header;
      }
    }

    $tableHeaderEntries[] = ['label' => 'Dibuat', 'value' => 'created_at', 'type' => 'date', 'isSearchable' => true, 'className' => 'text-[13px] w-[10%] md:w-[100px] !whitespace-pre'];
    $tableHeaderEntries[] = ['label' => 'Diperbarui', 'value' => 'updated_at', 'type' => 'date', 'isSearchable' => true, 'className' => 'text-[13px] w-[10%] md:w-[100px] !whitespace-pre'];
    $tableHeaderEntries[] = ['label' => 'Dihapus', 'value' => 'deleted_at', 'type' => 'date', 'className' => 'text-[13px] w-[10%] md:w-[100px] !whitespace-pre', 'isHidden' => "params.get('deleted_at') != 'show'"];
    $tableHeaderEntries[] = ['label' => 'Aksi', 'value' => 'id', 'type' => 'action'];


    // === Form Generation ===
    $configFormLayout = $this->configEntry['form_layout'] ?? [];

    if (!empty($configFormLayout)) {
      // Complex layout from config
      foreach ($configFormLayout as $row) {
        $rowProps = [];
        foreach ($row as $col) {
          if (isset($col['custom'])) {
            $rowProps[] = ['custom' => "(props) => <{$col['custom']} {...props} />"];
          } elseif (isset($col['form']) && isset($col['props'])) {
            // Check for options in props
            if (isset($col['props']['options'])) {
              $optionsVal = $col['props']['options'];
              if (Str::startsWith($optionsVal, 'props.')) {
                $varName = substr($optionsVal, 6); // remove 'props.'
                $extraOptions[$varName] = $varName;
              }
            }

            // New structure: Direct pass-through
            $props = $col['props'];

            // Inject Prefix for Select/Status
            if (isset($col['form']) && ($col['form'] === 'select' || $col['form'] === 'status')) {
              if (isset($props['name']) && !Str::endsWith($props['name'], '_id')) {
                $props['prefix'] = Str::snake($modelName) . '.' . $props['name'];
              }
            }

            $rowProps[] = [
              'form' => $col['form'],
              'props' => $props
            ];

            // Add to formFieldsData
            if (isset($props['name'])) {
              $fieldName = $props['name'];
              $formFieldsData .= "        {$fieldName}: '',\n";
            }
          } elseif (isset($col['field'])) {
            // Old structure: Field lookup
            $fieldName = $col['field'];
            $fieldConfig = $this->configEntry['columns'][$fieldName] ?? [];

            $props = [
              'name' => $fieldName,
              'label' => $col['label'] ?? ($fieldConfig['label'] ?? Str::headline($fieldName))
            ];

            if (isset($col['props'])) {
              if (isset($col['props']['options']) && Str::startsWith($col['props']['options'], 'props.')) {
                $varName = substr($col['props']['options'], 6);
                $extraOptions[$varName] = $varName;
              }
              $props = array_merge($props, $col['props']);
            }

            $type = $col['type'] ?? ($fieldConfig['type'] ?? 'input');

            if ($type === 'select' || $type === 'status') {
              if (!Str::endsWith($fieldName, '_id')) {
                $props['prefix'] = Str::snake($modelName) . '.' . $fieldName;
              }
            }

            $rowProps[] = [
              'form' => $type,
              'props' => $props
            ];

            // Add to formFieldsData
            $formFieldsData .= "        {$fieldName}: '',\n";
          }
        }
        $formProperties[] = $rowProps;
      }
    } else {
      // Fallback: standard linear form
      foreach ($columnMatches as $match) {
        $column = $match['column'];
        if (in_array($column, ['id', 'created_at', 'updated_at', 'user_id', 'deleted_at']))
          continue;

        $label = Str::headline($column);
        $formType = 'input';
        $formProps = [];

        if (Str::endsWith($column, '_id')) {
          $formType = 'select';
          $varName = Str::plural(Str::beforeLast($column, '_id'));
          $formProps['options'] = "props." . $varName;
          $extraOptions[$varName] = $varName; // Track this

        } elseif ($column === 'status') {
          $formType = 'select';
          $formProps['options'] = "props.status";
          $formProps['prefix'] = Str::snake($modelName) . '.status';
          $extraOptions['status'] = 'status'; // Track this

        } elseif (Str::endsWith($column, '_at')) {
          $formType = 'DatePicker';
        } elseif ($formType === 'select' && !Str::endsWith($column, '_id')) {
          $formProps['prefix'] = Str::snake($modelName) . '.' . $column;
        }

        $properties = [
          'form' => $formType,
          'props' => array_merge([
            'name' => $column,
            'label' => $label,
          ], $formProps)
        ];
        $formProperties[] = [$properties]; // encapsulate in single-item row

        $formFieldsData .= "        {$column}: '',\n";
      }
    }

    // Generate Validation Rules (Source of Truth: Config or Migration)
    $configValidations = $this->configEntry['validations'] ?? [];
    if (!empty($configValidations)) {
      foreach ($configValidations as $field => $rule) {
        $validationRules .= "            \"{$field}\" => \"{$rule}\",\n";
      }
    } else {
      // Fallback validation generation
      foreach ($columnMatches as $match) {
        $column = $match['column'];
        if (in_array($column, ['id', 'created_at', 'updated_at', 'user_id', 'deleted_at']))
          continue;

        $rule = Str::contains($match['options'], '->nullable()') ? 'nullable' : 'required';
        $validationRules .= "            \"{$column}\" => \"{$rule}\",\n";
      }
    }

    // Generate Defaults from Config
    $defaultValuesString = "";
    $configDefaults = $this->configEntry['defaults'] ?? [];
    foreach ($configDefaults as $field => $value) {
      $valExp = Str::startsWith($value, "\\") ? $value : var_export($value, true);
      $defaultValuesString .= "                \"{$field}\" => {$valExp},\n"; // Indent 16 spaces (4 tabs) to match stub context
    }

    // === Controller Logic for Options ===
    $optionsSource = $this->configEntry['options_source'] ?? [];
    $controllerGetDataLogic = "";

    // Merge explicitly defined sources with discovered extra options
    // ALSO: Scan defaults for potential Enums that should be available as options
    foreach ($configDefaults as $field => $value) {
      if (Str::startsWith($value, '\App\Enums\\') && Str::contains($value, '::')) {
        $enumClass = Str::before($value, '::');
        // Variable name: usually plural of field. type -> types, method -> methods
        // If field is 'status', we often use 'status' as variable too (based on existing logic), 
        // but let's try plural first or stick to convention.
        // Existing logic uses 'status' for 'status'. 
        // Let's use plural for others.
        $varName = Str::plural($field);
        if ($field === 'status')
          $varName = 'status'; // Exception for status to match existing pattern?

        if (!isset($optionsSource[$varName]) && !isset($extraOptions[$varName])) {
          // Add to extraOptions so it gets processed in the loop below
          $extraOptions[$varName] = $field; // Store mapping varName -> field (or just value)
        }
      }
    }

    // If extra option is NOT in optionsSource, we try to derive it.
    foreach ($extraOptions as $varName => $originField) {
      if (is_numeric($varName)) {
        $varName = $originField;
        $originField = $varName; // If array was non-assoc
      }

      if (!isset($optionsSource[$varName])) {

        // Intelligent Guessing
        $enumClass = null;

        // 1. Check if 'defaults' has a value that looks like an Enum (e.g. \App\Enums\BudgetStatusType::Draft)
        // We look for the singular key or the plural key (status or statuses)
        $defaultKey = $varName;
        // if varName is plural and not found, try singular (statuses -> status)
        if (!isset($configDefaults[$defaultKey])) {
          $singularKey = Str::singular($varName);
          if (isset($configDefaults[$singularKey])) {
            $defaultKey = $singularKey;
          }
        }

        if (isset($configDefaults[$defaultKey])) {
          $defaultValue = $configDefaults[$defaultKey];
          // Check if it looks like an Enum class usage: \App\Enums\Name::Case
          if (Str::startsWith($defaultValue, '\App\Enums\\') && Str::contains($defaultValue, '::')) {
            $enumClass = Str::before($defaultValue, '::');
          }
        }

        if ($enumClass) {
          $controllerGetDataLogic .= "    \${$varName} = {$enumClass}::getValues();\n";
          $optionsSource[$varName] = 'HANDLED_BY_LOGIC';

        } elseif ($varName === 'status' || $varName === 'statuses') {
          $controllerGetDataLogic .= "    \${$varName} = \\App\\Enums\\OriginStatusType::getValues();\n";
          // Add to optionsSource for formData loop below, effectively marking it handled
          $optionsSource[$varName] = 'HANDLED_BY_LOGIC';
        } elseif ($varName === 'roles') {
          // Try UserType
          $controllerGetDataLogic .= "    \${$varName} = \\App\\Enums\\UserType::getValues();\n";
          $optionsSource[$varName] = 'HANDLED_BY_LOGIC';
        } elseif ($varName === 'types') {
          // Generic Type? Maybe we can check model name or assume 'Type' enum exists
          // For now, let's leave it empty or comment TODO
          $controllerGetDataLogic .= "    // TODO: Define \${$varName} logic (e.g. Enum::getValues())\n";
          $controllerGetDataLogic .= "    \${$varName} = [];\n";
          $optionsSource[$varName] = 'HANDLED_BY_LOGIC';
        } else {
          // Is it a relation? users -> User::all()
          $singular = Str::singular($varName);
          $modelClass = Str::studly($singular);
          // We can try to guess App\Models\$modelClass
          $controllerGetDataLogic .= "    // Auto-derived for {$varName}\n";
          $controllerGetDataLogic .= "    \${$varName} = \\App\\Models\\{$modelClass}::selectOptions()->get();\n";
          $optionsSource[$varName] = 'HANDLED_BY_LOGIC';
        }
      }
    }

    foreach ($optionsSource as $varName => $source) {
      if ($source === 'HANDLED_BY_LOGIC')
        continue; // Already generated above

      if (is_array($source)) {
        $model = $source['model'];
        $labelRaw = $source['raw_label'] ?? 'name';

        $queryScope = isset($source['query']) ? "{$source['query']}()" : "query()";
        if (Str::startsWith($queryScope, "->")) {
          $queryScope = substr($queryScope, 2);
        }

        $modelClass = class_basename($model);
        $controllerGetDataLogic .= "    \${$varName} = \\{$model}::{$queryScope}->select('id as value', \\Illuminate\\Support\\Facades\\DB::raw(\"{$labelRaw} as label\"))->get();\n";
      } else {
        $modelClass = class_basename($source);
        $controllerGetDataLogic .= "    \${$varName} = \\{$source}::selectOptions()->get();\n";
      }
    }

    // Add logic to return these in getFormData
    $formDataReturnArray = "";
    foreach (array_keys($optionsSource) as $varName) {
      $formDataReturnArray .= "            \"{$varName}\" => \${$varName},\n";
    }


    // Convert to strings
    $jsTableHeaders = "";
    foreach ($tableHeaderEntries as $entry) {
      $jsTableHeaders .= '      ' . $this->convertArrayToJsObjectString($entry, 6) . ",\n";
    }

    return [
      '{{ tableHeaderReactArray }}' => rtrim($jsTableHeaders, ",\n"),
      '{{ validationRules }}' => $validationRules,
      '{{ formFieldsData }}' => $formFieldsData,
      '{{ useEffectHooks }}' => $useEffectHooks,
      '{{ formProperties }}' => $this->convertFormPropertiesArrayToJsString($formProperties),
      '{{ controllerGetDataLogic }}' => $controllerGetDataLogic,
      '{{ formDataReturnArray }}' => $formDataReturnArray,
      '{{ defaultValues }}' => $defaultValuesString,
      '{{ indexQueryLogic }}' => $this->generateIndexQueryLogic($tableHeaderEntries, $configColumns),
    ];
  }

  protected function generateIndexQueryLogic($tableHeaderEntries, $configColumns)
  {
    $logic = "";
    $relations = [];
    $processedFilterColumns = [];

    foreach ($tableHeaderEntries as $entry) {
      $column = $entry['value'];

      // Filter Logic for status and type (Direct columns only)
      if (!Str::contains($column, '.') && Str::contains($column, ['status', 'type']) && $column !== 'created_at' && $column !== 'updated_at') {
        if (!in_array($column, $processedFilterColumns)) {
          $logic .= "        \$query->when(\$request->filled(\"{$column}\"), fn(\$q) => \$q->where(\"{$column}\", \$request->{$column}));\n";
          $processedFilterColumns[] = $column;
        }
      }

      // Relation Logic detection
      $relation = null;
      if (Str::contains($column, '.')) {
        $relation = Str::before($column, '.');
      } elseif (Str::endsWith($column, '_id')) {
        $relation = Str::camel(Str::beforeLast($column, '_id'));
      }

      if ($relation && !in_array($relation, $relations)) {
        $relations[] = $relation;
      }
    }

    foreach ($relations as $relation) {
      if ($relation === 'user') {
        $logic .= "        \$query->with(\"user:id,name,role\");\n";
      } else {
        $logic .= "        \$query->with(\"{$relation}\");\n";
      }
    }

    return $logic;
  }

  // ... Methods (createController, createReactView, appendRoute ...) 
  // Copied from original but simplified where needed. 
  // IMPORTANT: I need to ensure placeholders in Stubs allow for strict replacement of {{ controllerGetDataLogic }}
  // The existing Controller/DefaultController.stub probably doesn't have {{ controllerGetDataLogic }}.
  // I need to update the stub OR find a way to inject it.
  // The existing stub has getFormData? 
  // Let's assume I can overwrite the stub logic.

  protected function createController(array $placeholders)
  {
    $stub = File::get($this->stubsPath . 'Controller/DefaultController.stub');

    // Custom injection for Controller logic
    // We might need to replace the whole getFormData method in stub if we want total control.
    // Or we inject into a specific placeholder. 
    // Existing stub likely doesn't have valid placeholders for this new dynamic logic.
    // I should probably manually construct the getFormData method content and replace a larger block.

    // But for now, let's just do standard replacement and hope the stub has {{ formDataLogic }} or I add it.
    // Since I cannot trust the stub content without reading it, I will inject my own getFormData method body by regex replacing the stub method if needed, OR just adding a generic placeholder if I can modify stubs.
    // The user said "jika custom maka berikan default dari stubs". 
    // I will assume standard stubs.

    // Let's look at getFormData in Controller stub logic.
    // Ideally I should update the STUB too if it doesn't fit. 
    // But I will try to patch it here.

    $content = str_replace(array_keys($placeholders), array_values($placeholders), $stub);

    // Inject Relations Logic into getFormData
    if (!empty($placeholders['{{ controllerGetDataLogic }}'])) {
      // Find return statement in getFormData and inject before it
      $content = preg_replace(
        '/(protected function getFormData.*?\{)(.*?)(\s*return \[)/s',
        '$1$2' . "\n" . $placeholders['{{ controllerGetDataLogic }}'] . '$3' . "\n" . $placeholders['{{ formDataReturnArray }}'],
        $content
      );
    }

    $controllerDir = app_path('Http/Controllers/' . $placeholders['{{ AdminPath }}']);
    if (!File::exists($controllerDir))
      File::makeDirectory($controllerDir, 0755, true);

    $controllerPath = $controllerDir . '/' . $placeholders['{{ ModelName }}'] . 'Controller.php';
    File::put($controllerPath, $content);
    $this->info("Controller created: {$controllerPath}");
  }

  protected function createReactView(string $viewType, array $placeholders)
  {
    $stub = File::get($this->stubsPath . "View/$viewType.React.stub");
    $content = str_replace(array_keys($placeholders), array_values($placeholders), $stub);
    $content = str_replace("'js::setData'", "setData", $content);

    // Custom handling for MainForm custom components
    if ($viewType === 'MainForm') {
      // Check if we have PatientInfoCard etc. 
      // Ideally we append the stub code for them.
      if (Str::contains($content, 'PatientInfoCard')) {
        // Append generic component stub at end
        $content .= "\n\nconst PatientInfoCard = (props) => { return <div className='p-4 border'>Patient Info Card (Stub)</div>; };";
      }
      if (Str::contains($content, 'FormLabel')) {
        $content .= "\n\nconst FormLabel = () => { return <div className='font-bold text-lg'>Form Label</div>; };";
      }
      if (Str::contains($content, 'DefaultForm')) {
        $content .= "\n\nconst DefaultForm = (props) => { return <div>Default Form (Stub)</div>; };";
      }
    }

    $viewDir = resource_path('js/Pages/' . $placeholders['{{ AdminPath }}'] . '/' . $placeholders['{{ ModelName }}']);
    if ($viewType == 'MainForm') {
      $viewDir .= '/Form';
    }

    if (!File::exists($viewDir))
      File::makeDirectory($viewDir, 0755, true);

    $viewPath = $viewDir . '/' . $viewType . '.jsx';
    File::put($viewPath, $content);
    $this->info("React view created: {$viewPath}");

    $customComponents = $this->configEntry['custom_components'] ?? [];
    foreach ($customComponents as $compName => $compCode) {
      $compPath = $viewDir . '/' . $compName . '.jsx';
      if (!File::exists($compPath)) {
        File::put($compPath, $compCode);
        $this->info("Custom component created: {$compPath}");
      }
    }
  }

  // ... Copy paste other methods (appendRoute, appendSidebar, rollback...) with no changes as they are utility

  protected function appendRouteToWebPhp(array $placeholders)
  {
    $backofficeRoutePath = base_path('routes/backoffice.php');
    $webRoutePath = base_path('routes/web.php');

    $targetRoutePath = File::exists($backofficeRoutePath) ? $backofficeRoutePath : $webRoutePath;
    $targetRouteFileName = Str::afterLast($targetRoutePath, '/');

    $controllerClass = $placeholders['{{ SubNamespace }}']
      . "\\"
      . $placeholders['{{ ModelName }}']
      . "Controller";

    $newRoute = "    Route::resource('"
      . substr($placeholders['{{ pageUrl }}'], 5)
      . "', {$controllerClass}::class)->only('php::resourceMethod');";
    $newRoute = str_replace("'php::resourceMethod'", '[...$resourceMethod]', $newRoute);

    if (!File::exists($targetRoutePath)) {
      $this->warn("Route file not found, skipping route append.");
      return;
    }

    $routeFileContent = File::get($targetRoutePath);

    // Cegah duplikasi
    if (Str::contains($routeFileContent, $newRoute)) {
      $this->warn("Route for '" . $placeholders['{{ pageUrl }}'] . "' already exists in routes/{$targetRouteFileName}. Skipping.");
      return;
    }

    $updatedFileContent = $routeFileContent;

    // 1️⃣ Cek blok admin group
    $pattern = '/Route::middleware\(\s*\[\s*\'auth\'\s*,\s*\'role:admin\'\s*\]\s*\)\s*->prefix\(\s*\'admin\'\s*\)\s*->name\(\s*\'admin\.\'\s*\)\s*->group\(\s*function\s*\(\)\s*(?:\s*use\s*\([^)]*\)\s*)?\{([\s\S]*?)\}\s*\);/m';
    if (preg_match($pattern, $routeFileContent, $matches)) {
      $adminGroupContent = $matches[0];
      $newAdminGroupContent = substr($adminGroupContent, 0, -3) // buang "});"
        . $newRoute . "\n});";
      $updatedFileContent = str_replace($adminGroupContent, $newAdminGroupContent, $routeFileContent);

      // 2️⃣ Kalau tidak ada admin group, cek middleware auth group
    } elseif (preg_match('/Route::middleware\(\'auth\'\)->group\(function\s*\(\)\s*\{[\s\S]*?\};/m', $routeFileContent, $matches)) {
      $authGroupContent = $matches[0];
      $newAuthGroupContent = substr($authGroupContent, 0, -3)
        . "\n    " . $newRoute . "\n});";
      $updatedFileContent = str_replace($authGroupContent, $newAuthGroupContent, $routeFileContent);

      // 3️⃣ Kalau tidak ada group sama sekali, append di akhir file
    } else {
      $updatedFileContent = rtrim($routeFileContent) . "\n\n" . $newRoute . "\n";
    }

    File::put($targetRoutePath, $updatedFileContent);
    $this->info("Route '{$newRoute}' appended to routes/{$targetRouteFileName}");
  }

  protected function appendSidebarMenuEntry(array $placeholders)
  {
    $jsonDir = resource_path('json');
    $jsonPath = $jsonDir . '/sidebarMenu.json';

    // Pastikan direktori ada
    if (!File::exists($jsonDir)) {
      File::makeDirectory($jsonDir, 0755, true);
      $this->info("Created directory: {$jsonDir}");
    }

    // Jika file belum ada, inisialisasi sebagai array kosong
    if (!File::exists($jsonPath)) {
      File::put($jsonPath, json_encode([], JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES));
      $this->info("Created new sidebarMenu.json file.");
    }

    // Baca isi JSON
    $menu = json_decode(File::get($jsonPath), true);
    if (json_last_error() !== JSON_ERROR_NONE) {
      $this->error("Failed to parse sidebarMenu.json: " . json_last_error_msg());
      return;
    }

    // Ambil variabel dari placeholder
    $pageUrl = $placeholders['{{ pageUrl }}'];
    $pageLabel = $placeholders['{{ pageLabel }}'];
    $iconClass = $this->option('icon')
      ?? ($this->iconDictionary[$placeholders['{{ tableName }}']] ?? 'fas fa-cube fa-fw');

    // Use config icon if available
    if (isset($this->configEntry['icon'])) {
      $iconClass = $this->configEntry['icon'];
    }

    $prefix = $this->option('prefix_url')
      ? Str::of($this->option('prefix_url'))->after('admin/')->before('/')
      : null;

    $isDropdown = !empty($prefix);

    // ==== Cegah duplikasi ====
    foreach ($menu as $item) {
      if (isset($item['link']) && $item['link'] === "/{$pageUrl}") {
        $this->warn("Sidebar menu entry for '{$pageLabel}' already exists. Skipping.");
        return;
      }
      if (isset($item['dropdown'])) {
        foreach ($item['dropdown'] as $child) {
          if (isset($child['link']) && $child['link'] === "/{$pageUrl}") {
            $this->warn("Sidebar dropdown entry for '{$pageLabel}' already exists. Skipping.");
            return;
          }
        }
      }
    }

    // ==== Tambah entry ====
    if ($isDropdown) {
      $parentTitle = Str::of($prefix)->headline();
      $parentIcon = $this->iconDictionary[Str::plural($prefix)] ?? 'fas fa-cube fa-fw';
      $childMenuItem = [
        'title' => $pageLabel,
        'link' => "/{$pageUrl}"
      ];

      $foundParent = false;
      foreach ($menu as &$item) {
        if ($item['title'] === $parentTitle && isset($item['dropdown'])) {
          $item['dropdown'][] = $childMenuItem;
          $foundParent = true;
          break;
        }
      }

      if (!$foundParent) {
        $menu[] = [
          'title' => $parentTitle,
          'fa_icon' => $parentIcon,
          'icon_provider' => 'fontawesome',
          'dropdown' => [$childMenuItem],
        ];
      }
    } else {
      $menu[] = [
        'title' => $pageLabel,
        'fa_icon' => $iconClass,
        'icon_provider' => 'fontawesome',
        'link' => "/{$pageUrl}",
      ];
    }

    // ==== Simpan kembali ke JSON ====
    File::put($jsonPath, json_encode($menu, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES));
    $this->info("Sidebar menu entry for '{$pageLabel}' added to sidebarMenu.json");
  }

  protected function runFormatter(string $modelName, array $placeholders)
  {
    $filesToFormat = [
      app_path("Http/Controllers/{$placeholders['{{ AdminPath }}']}/{$modelName}Controller.php"),
      resource_path("js/Pages/{$placeholders['{{ AdminPath }}']}/$modelName/Index.jsx"),
      resource_path("js/Pages/{$placeholders['{{ AdminPath }}']}/$modelName/Create.jsx"),
      resource_path("js/Pages/{$placeholders['{{ AdminPath }}']}/$modelName/Edit.jsx"),
      resource_path("js/Pages/{$placeholders['{{ AdminPath }}']}/$modelName/Show.jsx"),
      resource_path("js/Pages/{$placeholders['{{ AdminPath }}']}/$modelName/Form/MainForm.jsx"),
    ];

    $this->info("Menjalankan Prettier untuk merapikan file...");

    $command = ['npx', 'prettier', '--write', ...$filesToFormat];

    $process = new Process($command);
    $process->run();

    if (!$process->isSuccessful()) {
      $this->error("Gagal menjalankan Prettier.");
    } else {
      $this->info("File-file berhasil dirapikan.");
    }
  }

  protected function rollbackController(array $placeholders)
  {
    $controllerDir = app_path('Http/Controllers/' . $placeholders['{{ AdminPath }}']);
    $controllerPath = $controllerDir . '/' . $placeholders['{{ ModelName }}'] . 'Controller.php';

    if (File::exists($controllerPath)) {
      File::delete($controllerPath);
      $this->info("Controller deleted: {$controllerPath}");

      if (File::isDirectory($controllerDir) && count(File::files($controllerDir)) === 0) {
        File::deleteDirectory($controllerDir);
        $this->info("Controller directory deleted: {$controllerDir}");
      }
    } else {
      $this->warn("Controller not found, skipping rollback: {$controllerPath}");
    }
  }

  protected function rollbackReactViews(array $placeholders)
  {
    $adminPath = $placeholders['{{ AdminPath }}'];
    $modelName = $placeholders['{{ ModelName }}'];

    $baseViewDir = resource_path("js/Pages/{$adminPath}/{$modelName}");
    $formViewDir = resource_path("js/Pages/{$adminPath}/{$modelName}/Form");

    $filesToDelete = [
      "{$baseViewDir}/Index.jsx",
      "{$baseViewDir}/Create.jsx",
      "{$baseViewDir}/Edit.jsx",
      "{$baseViewDir}/Show.jsx",
      "{$formViewDir}/MainForm.jsx",
    ];

    foreach ($filesToDelete as $file) {
      if (File::exists($file)) {
        File::delete($file);
        $this->info("React view deleted: {$file}");
      }
    }

    if (File::isDirectory($formViewDir) && count(File::allFiles($formViewDir)) === 0) {
      File::deleteDirectory($formViewDir);
    }

    if (File::isDirectory($baseViewDir) && count(File::allFiles($baseViewDir)) === 0) {
      File::deleteDirectory($baseViewDir);
    }
  }

  protected function rollbackRoute(array $placeholders)
  {
    $backofficeRoutePath = base_path('routes/backoffice.php');
    $webRoutePath = base_path('routes/web.php');

    $targetRoutePath = File::exists($backofficeRoutePath) ? $backofficeRoutePath : $webRoutePath;

    $controllerClass = $placeholders['{{ SubNamespace }}']
      . "\\"
      . $placeholders['{{ ModelName }}']
      . "Controller";

    $newRoute = "    Route::resource('"
      . substr($placeholders['{{ pageUrl }}'], 5)
      . "', {$controllerClass}::class)->only('php::resourceMethod');";
    $newRoute = str_replace("'php::resourceMethod'", '[...$resourceMethod]', $newRoute);

    if (File::exists($targetRoutePath)) {
      $routeFileContent = File::get($targetRoutePath);
      $updatedFileContent = preg_replace('/\s*' . preg_quote($newRoute, '/') . '/', "", $routeFileContent);
      File::put($targetRoutePath, $updatedFileContent);
      $this->info("Route rolled back.");
    }
  }

  protected function convertArrayToJsObjectString(array $array, int $indent = 0): string
  {
    $space = str_repeat(' ', $indent);
    $jsString = "{\n";
    $entries = [];
    foreach ($array as $key => $value) {
      $entry = $space . "  '{$key}': ";
      if (is_array($value)) {
        $entry .= $this->convertArrayToJsObjectString($value, $indent + 2);
      } elseif (is_string($value)) {
        if (Str::startsWith($value, ['(props) =>', '(e) =>', '() =>', 'e =>']) || Str::contains($value, '=>')) {
          // Arrow function or component
          $entry .= $value;
        } elseif ($value === 'setData' || $value === 'true' || $value === 'false' || is_numeric($value) || Str::startsWith($value, 'props.') || Str::startsWith($value, 'params.get')) {
          $entry .= $value;
        } else {
          $entry .= "'{$value}'";
        }
      } elseif (is_bool($value)) {
        $entry .= $value ? 'true' : 'false';
      } elseif (is_numeric($value)) {
        $entry .= $value;
      } else {
        $entry .= "'{$value}'";
      }
      $entries[] = $entry;
    }
    $jsString .= implode(",\n", $entries);
    $jsString .= "\n" . $space . "}";
    return $jsString;
  }

  protected function convertFormPropertiesArrayToJsString(array $properties, int $chunkSize = 2): string
  {
    // $properties structure: [ [row1_item1, row1_item2], [row2_item1] ]
    // It is already chunked by rows based on config

    $outerJsString = "[\n";
    $chunkStrings = [];

    foreach ($properties as $row) {
      $innerJsString = "  [\n";
      $itemStrings = [];

      foreach ($row as $item) {
        if (is_array($item)) {
          $itemStrings[] = $this->convertArrayToJsObjectString($item, 4);
        }
      }

      $innerJsString .= implode(",\n", $itemStrings) . "\n";
      $innerJsString .= "  ]";
      $chunkStrings[] = $innerJsString;
    }

    $outerJsString .= implode(",\n", $chunkStrings) . "\n";
    $outerJsString .= "]";

    return $outerJsString;
  }
}
