<?php

declare(strict_types=1);

namespace App\Filament\Concerns;

use Filament\Tables\Columns\IconColumn;
use Filament\Tables\Columns\ImageColumn;
use Filament\Tables\Columns\TextColumn;
use Illuminate\Support\Facades\File;
use Illuminate\Support\Str;
use ReflectionClass;

trait CanGenerateTables
{
    protected function getTableComponents(): array
    {
        $components = [];
        $tableComponentsPath = app_path('Filament/Components/Table');

        if (! File::exists($tableComponentsPath)) {
            return [];
        }

        $files = File::files($tableComponentsPath);

        foreach ($files as $file) {
            $className = 'App\\Filament\\Components\\Table\\' . Str::before($file->getFilename(), '.php');
            if (! class_exists($className)) {
                continue;
            }

            $reflection = new ReflectionClass($className);
            if ($reflection->isAbstract()) {
                continue;
            }

            // Map component to column name (e.g., StatusSelect -> status_id)
            $componentName = $reflection->getShortName();
            $types = [
                'TextColumn',
                'ImageColumn',
                'IconColumn',
            ];
            foreach ($types as $type) {
                $componentName = str_replace($type, '', $componentName);
            }
            $columnName = Str::snake($componentName);
            $components[$columnName] = [
                'type' => '\\' . $className,
                'constructor' => 'create', // Assume create() method
                'methods' => [],
                'component_type' => 'custom',
            ];
        }

        return $components;
    }

    protected function getResourceTableColumns(string $model): string
    {
        $model = $this->getModel($model);

        if (blank($model)) {
            return '//';
        }

        $schema = $this->getModelSchema($model);
        $table = $this->getModelTable($model);

        $columns = [];
        $tableComponents = $this->getTableComponents();

        foreach ($schema->getColumns($table) as $column) {
            if ($column['auto_increment']) {
                continue;
            }

            $type = $this->parseColumnType($column);

            if (in_array($type['name'], [
                'json',
                'text',
            ])) {
                continue;
            }

            $columnName = $column['name'];

            if (str($columnName)->endsWith([
                '_token',
            ])) {
                continue;
            }

            if (str($columnName)->contains([
                'password',
            ])) {
                continue;
            }

            $columnData = [];
            if (str($columnName)->endsWith('_id') || str($columnName)->endsWith('_by')) {
                if (str($columnName)->endsWith('_id')) {
                    $standardRelationName = explode('_', $columnName)[0];
                } else {
                    $standardRelationName = $columnName;
                }
                $guessedRelationshipName = $this->guessBelongsToRelationshipName($columnName, $model);

                if (isset($tableComponents[$standardRelationName])) {
                    $customComponent = $tableComponents[$standardRelationName];
                    $columnName = $standardRelationName;
                    $columnData['type'] = $customComponent['type'];
                    $columnData['create'] = 'create';
                    $columnData['component_type'] = 'custom';
                } else {
                    if (filled($guessedRelationshipName)) {
                        $guessedRelationshipTitleColumnName = $this->guessBelongsToRelationshipTitleColumnName($columnName, app($model)->{$guessedRelationshipName}()->getModel()::class);
                        $columnName = "{$guessedRelationshipName}.{$guessedRelationshipTitleColumnName}";
                    }
                    $columnData['type'] = TextColumn::class;
                }
            } else {
                // Check for custom form component
                if (isset($tableComponents[$columnName])) {
                    $customComponent = $tableComponents[$columnName];
                    $columnData['type'] = $customComponent['type'];
                    $columnData[$customComponent['constructor']] = [];
                    if (isset($customComponent['component_type'])) {
                        $columnData['component_type'] = $customComponent['component_type'];
                    }
                    foreach ($customComponent['methods'] as $method => $params) {
                        $columnData[$method] = $params;
                    }
                } else {
                    if (in_array($columnName, [
                        'id',
                        'sku',
                        'uuid',
                    ])) {
                        $columnData['label'] = [Str::upper($columnName)];
                    }

                    if ($type['name'] === 'boolean') {
                        $columnData['type'] = IconColumn::class;
                        $columnData['boolean'] = [];
                    } else {
                        $columnData['type'] = match (true) {
                            $columnName === 'image', str($columnName)->startsWith('image_'), str($columnName)->contains('_image_'), str($columnName)->endsWith('_image') => ImageColumn::class,
                            default => TextColumn::class,
                        };

                        if (in_array($type['name'], [
                            'string',
                            'char',
                        ]) && ($columnData['type'] === TextColumn::class)) {
                            $columnData['searchable'] = [];
                        }

                        if (in_array($type['name'], [
                            'date',
                        ])) {
                            $columnData['date'] = [];
                            $columnData['sortable'] = [];
                        }

                        if (in_array($type['name'], [
                            'datetime',
                            'timestamp',
                        ])) {
                            $columnData['dateTime'] = [];
                            $columnData['sortable'] = [];
                        }

                        if (in_array($type['name'], [
                            'integer',
                            'decimal',
                            'float',
                            'double',
                            'money',
                        ])) {
                            $columnData[in_array($columnName, [
                                'cost',
                                'money',
                                'price',
                            ]) || $type['name'] === 'money' ? 'money' : 'numeric'] = [];
                            $columnData['sortable'] = [];
                        }
                    }

                    if (in_array($columnName, [
                        'created_at',
                        'updated_at',
                        'deleted_at',
                    ])) {
                        $standardRelationName = explode('_', $columnName)[0];

                        if (isset($tableComponents[$standardRelationName])) {
                            $customComponent = $tableComponents[$standardRelationName];
                            $columnName = $standardRelationName;
                            $columnData['type'] = $customComponent['type'];
                            $columnData['create'] = 'create';
                            $columnData['component_type'] = 'custom';
                            unset($columnData['dateTime'],$columnData['sortable']);
                        } else {
                            $columnData['toggleable'] = ['isToggledHiddenByDefault' => true];
                        }
                    }
                }
            }
            $columns[$columnName] = $columnData;
        }

        $output = count($columns) ? '' : '//';

        foreach ($columns as $columnName => $columnData) {
            if (! isset($columnData['component_type'])) {
                $output .= (string) str($columnData['type'])->after('Filament\\');
            } else {
                $output .= (string) str($columnData['type']);
            }

            if (isset($columnData['create'])) {
                if ($columnName == 'status') {
                    $output .= '::create(' . class_basename($model) . '::class)';
                } else {
                    $output .= '::create()';
                }
            } else {
                $output .= '::make(\'';
                $output .= $columnName;
                $output .= '\')';
            }

            unset($columnData['type'], $columnData['create'], $columnData['component_type']);

            // Configuration
            foreach ($columnData as $methodName => $parameters) {
                $output .= '->';
                $output .= $methodName;
                $output .= '(';
                $output .= collect($parameters)
                    ->map(function (mixed $parameterValue, int|string $parameterName): string {
                        $parameterValue = match (true) {
                            /** @phpstan-ignore-next-line */
                            is_bool($parameterValue) => $parameterValue ? 'true' : 'false',
                            /** @phpstan-ignore-next-line */
                            is_null($parameterValue) => 'null',
                            is_numeric($parameterValue) => $parameterValue,
                            default => "'{$parameterValue}'",
                        };

                        if (is_numeric($parameterName)) {
                            return $parameterValue;
                        }

                        return "{$parameterName}: {$parameterValue}";
                    })
                    ->implode(', ');
                $output .= ')';
            }

            // Termination
            $output .= ',';

            if (! (array_key_last($columns) === $columnName)) {
                $output .= PHP_EOL;
            }
        }

        return $output;
    }
}
