<?php

declare(strict_types=1);

namespace App\Framework\Console;

use ReflectionMethod;
use ReflectionNamedType;
use ReflectionParameter;
use ReflectionUnionType;

/**
 * Analyzes command method signatures to extract parameter information
 */
final readonly class ParameterInspector
{
    /**
     * Extract parameter information from a command method
     */
    public function inspectCommand(object $command, string $methodName = '__invoke'): array
    {
        try {
            $reflection = new ReflectionMethod($command, $methodName);
            $parameters = $reflection->getParameters();

            $parameterInfo = [];

            foreach ($parameters as $index => $parameter) {
                // Skip ConsoleInput and ConsoleOutput parameters
                if ($this->isFrameworkParameter($parameter)) {
                    continue;
                }

                $parameterInfo[] = $this->analyzeParameter($parameter, $index);
            }

            return $parameterInfo;
        } catch (\ReflectionException $e) {
            return [];
        }
    }

    /**
     * Check if parameter is a framework parameter (ConsoleInput/ConsoleOutput)
     */
    private function isFrameworkParameter(ReflectionParameter $parameter): bool
    {
        $type = $parameter->getType();

        if (! $type instanceof ReflectionNamedType) {
            return false;
        }

        $typeName = $type->getName();

        return in_array($typeName, [
            'App\Framework\Console\ConsoleInput',
            'App\Framework\Console\ConsoleOutput',
            'App\Framework\Console\Input\ConsoleInput',
            'App\Framework\Console\Output\ConsoleOutput',
        ]);
    }

    /**
     * Analyze a single parameter
     */
    private function analyzeParameter(ReflectionParameter $parameter, int $position): array
    {
        $info = [
            'name' => $parameter->getName(),
            'position' => $position,
            'type' => $this->getParameterType($parameter),
            'required' => ! $parameter->isOptional(),
            'default' => $parameter->isDefaultValueAvailable() ? $parameter->getDefaultValue() : null,
            'description' => $this->generateDescription($parameter),
            'validation_rules' => $this->inferValidationRules($parameter),
            'input_type' => $this->determineInputType($parameter),
        ];

        return $info;
    }

    /**
     * Get parameter type information
     */
    private function getParameterType(ReflectionParameter $parameter): array
    {
        $type = $parameter->getType();

        if (! $type) {
            return ['name' => 'mixed', 'nullable' => true];
        }

        if ($type instanceof ReflectionNamedType) {
            return [
                'name' => $type->getName(),
                'nullable' => $type->allowsNull(),
                'builtin' => $type->isBuiltin(),
            ];
        }

        if ($type instanceof ReflectionUnionType) {
            $types = [];
            foreach ($type->getTypes() as $unionType) {
                if ($unionType instanceof ReflectionNamedType) {
                    $types[] = $unionType->getName();
                }
            }

            return [
                'name' => implode('|', $types),
                'nullable' => $type->allowsNull(),
                'union' => true,
            ];
        }

        return ['name' => 'mixed', 'nullable' => true];
    }

    /**
     * Generate human-readable description for parameter
     */
    private function generateDescription(ReflectionParameter $parameter): string
    {
        $name = $parameter->getName();
        $type = $this->getParameterType($parameter);

        // Convert camelCase to readable format
        $readable = preg_replace('/([a-z])([A-Z])/', '$1 $2', $name);
        $readable = ucfirst(strtolower($readable));

        // Add type information
        if (isset($type['name']) && $type['name'] !== 'mixed') {
            $typeName = $this->getReadableTypeName($type['name']);

            return "{$readable} ({$typeName})";
        }

        return $readable;
    }

    /**
     * Convert technical type names to readable format
     */
    private function getReadableTypeName(string $typeName): string
    {
        return match ($typeName) {
            'string' => 'text',
            'int' => 'number',
            'float' => 'decimal',
            'bool' => 'yes/no',
            'array' => 'list',
            default => basename(str_replace('\\', '/', $typeName))
        };
    }

    /**
     * Infer validation rules based on parameter type and name
     */
    private function inferValidationRules(ReflectionParameter $parameter): array
    {
        $rules = [];
        $type = $this->getParameterType($parameter);
        $name = strtolower($parameter->getName());

        // Required validation
        if (! $parameter->isOptional()) {
            $rules['required'] = true;
        }

        // Type-based validations
        switch ($type['name']) {
            case 'int':
                $rules['type'] = 'integer';
                if (str_contains($name, 'port')) {
                    $rules['min'] = 1;
                    $rules['max'] = 65535;
                } elseif (str_contains($name, 'count') || str_contains($name, 'limit')) {
                    $rules['min'] = 0;
                }

                break;

            case 'string':
                $rules['type'] = 'string';
                if (str_contains($name, 'email')) {
                    $rules['format'] = 'email';
                } elseif (str_contains($name, 'url')) {
                    $rules['format'] = 'url';
                } elseif (str_contains($name, 'path')) {
                    $rules['format'] = 'path';
                }

                break;

            case 'bool':
                $rules['type'] = 'boolean';

                break;

            case 'float':
                $rules['type'] = 'number';

                break;
        }

        return $rules;
    }

    /**
     * Determine the best input type for the parameter
     */
    private function determineInputType(ReflectionParameter $parameter): string
    {
        $type = $this->getParameterType($parameter);
        $name = strtolower($parameter->getName());

        // Special cases based on parameter name
        if (str_contains($name, 'password') || str_contains($name, 'secret')) {
            return 'password';
        }

        if (str_contains($name, 'email')) {
            return 'email';
        }

        if (str_contains($name, 'file') || str_contains($name, 'path')) {
            return 'file';
        }

        if (str_contains($name, 'url')) {
            return 'url';
        }

        // Type-based input types
        return match ($type['name']) {
            'bool' => 'boolean',
            'int' => 'number',
            'float' => 'decimal',
            'array' => 'list',
            default => 'text'
        };
    }
}
