<?php

declare(strict_types=1);

namespace App\Framework\Console;

/**
 * Generates help text for console commands based on their argument definitions
 */
final readonly class HelpGenerator
{
    public function __construct(
        private ConsoleOutputInterface $output
    ) {
    }

    /**
     * Generate help text for a command
     *
     * @param ArgumentDefinition[] $definitions
     */
    public function generate(string $commandName, array $definitions, string $description = ''): string
    {
        $help = [];

        // Command description
        if (! empty($description)) {
            $help[] = $description;
            $help[] = '';
        }

        // Usage line
        $help[] = $this->generateUsageLine($commandName, $definitions);
        $help[] = '';

        // Arguments section
        $positionalArgs = $this->getPositionalArguments($definitions);
        if (! empty($positionalArgs)) {
            $help[] = ConsoleColor::BRIGHT_YELLOW->toAnsi() . 'Arguments:' . ConsoleColor::RESET->toAnsi();
            foreach ($positionalArgs as $definition) {
                $help[] = $this->formatArgumentHelp($definition);
            }
            $help[] = '';
        }

        // Options section
        $options = $this->getOptions($definitions);
        if (! empty($options)) {
            $help[] = ConsoleColor::BRIGHT_YELLOW->toAnsi() . 'Options:' . ConsoleColor::RESET->toAnsi();
            foreach ($options as $definition) {
                $help[] = $this->formatOptionHelp($definition);
            }
            $help[] = '';
        }

        // Examples
        $examples = $this->generateExamples($commandName, $definitions);
        if (! empty($examples)) {
            $help[] = ConsoleColor::BRIGHT_YELLOW->toAnsi() . 'Examples:' . ConsoleColor::RESET->toAnsi();
            foreach ($examples as $example) {
                $help[] = '  ' . ConsoleColor::CYAN->toAnsi() . $example . ConsoleColor::RESET->toAnsi();
            }
        }

        return implode(PHP_EOL, $help);
    }

    /**
     * Display help text directly to console output
     *
     * @param ArgumentDefinition[] $definitions
     */
    public function display(string $commandName, array $definitions, string $description = ''): void
    {
        $help = $this->generate($commandName, $definitions, $description);
        $this->output->writeLine($help);
    }

    /**
     * Generate usage line
     *
     * @param ArgumentDefinition[] $definitions
     */
    private function generateUsageLine(string $commandName, array $definitions): string
    {
        $usage = "Usage: php console {$commandName}";

        // Add positional arguments
        $positionalArgs = $this->getPositionalArguments($definitions);
        foreach ($positionalArgs as $definition) {
            if ($definition->required) {
                $usage .= " <{$definition->name}>";
            } else {
                $usage .= " [{$definition->name}]";
            }
        }

        // Add options indicator
        $options = $this->getOptions($definitions);
        if (! empty($options)) {
            $usage .= ' [options]';
        }

        return ConsoleColor::BRIGHT_CYAN->toAnsi() . $usage . ConsoleColor::RESET->toAnsi();
    }

    /**
     * Format help for positional argument
     */
    private function formatArgumentHelp(ArgumentDefinition $definition): string
    {
        $name = str_pad($definition->name, 20);
        $description = $definition->description;

        if ($definition->default !== null) {
            $defaultString = $this->convertValueToString($definition->default);
            $description .= " [default: {$defaultString}]";
        }

        if (! empty($definition->allowedValues)) {
            $description .= " [choices: " . implode(', ', $definition->allowedValues) . "]";
        }

        return "  {$name} {$description}";
    }

    /**
     * Format help for option
     */
    private function formatOptionHelp(ArgumentDefinition $definition): string
    {
        $names = $this->formatOptionNames($definition);
        $paddedNames = str_pad($names, 25);

        $description = $definition->description;

        if ($definition->type !== ArgumentType::BOOLEAN) {
            $description .= " ({$definition->type->getDescription()})";
        }

        if ($definition->default !== null && $definition->default !== false) {
            $defaultStr = $this->convertValueToString($definition->default);
            $description .= " [default: {$defaultStr}]";
        }

        if (! empty($definition->allowedValues)) {
            $description .= " [choices: " . implode(', ', $definition->allowedValues) . "]";
        }

        if ($definition->required) {
            $description .= ConsoleColor::RED->toAnsi() . ' (required)' . ConsoleColor::RESET->toAnsi();
        }

        return "  {$paddedNames} {$description}";
    }

    /**
     * Format option names (both long and short)
     */
    private function formatOptionNames(ArgumentDefinition $definition): string
    {
        $names = "--{$definition->name}";

        if ($definition->shortName) {
            $names = "-{$definition->shortName}, {$names}";
        }

        if ($definition->type !== ArgumentType::BOOLEAN) {
            if (! empty($definition->allowedValues)) {
                $names .= "=" . implode('|', $definition->allowedValues);
            } else {
                $names .= "={$definition->type->getExample()}";
            }
        }

        return $names;
    }

    /**
     * Get positional arguments from definitions
     *
     * @param ArgumentDefinition[] $definitions
     * @return ArgumentDefinition[]
     */
    private function getPositionalArguments(array $definitions): array
    {
        return array_filter($definitions, function ($definition) {
            return $definition->type !== ArgumentType::BOOLEAN && $definition->shortName === null;
        });
    }

    /**
     * Get options (flags and named options) from definitions
     *
     * @param ArgumentDefinition[] $definitions
     * @return ArgumentDefinition[]
     */
    private function getOptions(array $definitions): array
    {
        return array_filter($definitions, function ($definition) {
            return $definition->type === ArgumentType::BOOLEAN || $definition->shortName !== null;
        });
    }

    /**
     * Generate example usage
     *
     * @param ArgumentDefinition[] $definitions
     * @return string[]
     */
    private function generateExamples(string $commandName, array $definitions): array
    {
        $examples = [];

        // Basic example with required arguments
        $requiredArgs = array_filter($definitions, fn ($def) => $def->required);
        if (! empty($requiredArgs)) {
            $example = "php console {$commandName}";
            foreach ($requiredArgs as $definition) {
                if ($definition->type === ArgumentType::BOOLEAN) {
                    $example .= " --{$definition->name}";
                } else {
                    $example .= " {$definition->type->getExample()}";
                }
            }
            $examples[] = $example;
        }

        // Example with options
        $optionalOptions = array_filter($definitions, fn ($def) => ! $def->required && $def->type !== ArgumentType::BOOLEAN);
        if (! empty($optionalOptions)) {
            $example = "php console {$commandName}";

            // Add required args first
            foreach ($requiredArgs as $definition) {
                if ($definition->type !== ArgumentType::BOOLEAN) {
                    $example .= " {$definition->type->getExample()}";
                }
            }

            // Add some optional options
            $counter = 0;
            foreach ($optionalOptions as $definition) {
                if ($counter >= 2) {
                    break;
                } // Limit examples

                if ($definition->shortName) {
                    $example .= " -{$definition->shortName} {$definition->type->getExample()}";
                } else {
                    $example .= " --{$definition->name}={$definition->type->getExample()}";
                }
                $counter++;
            }

            if ($example !== "php console {$commandName}") {
                $examples[] = $example;
            }
        }

        return $examples;
    }

    /**
     * Generate quick help summary for command list
     */
    public function generateQuickHelp(string $commandName, string $description): string
    {
        $name = str_pad($commandName, 20);

        return "  {$name} {$description}";
    }

    /**
     * Convert value to string for display
     */
    private function convertValueToString(mixed $value): string
    {
        if (is_string($value)) {
            return $value;
        }

        if ($value instanceof \BackedEnum) {
            return (string) $value->value;
        }

        if ($value instanceof \UnitEnum) {
            return $value->name;
        }

        if (is_bool($value)) {
            return $value ? 'true' : 'false';
        }

        if (is_scalar($value)) {
            return (string) $value;
        }

        if (method_exists($value, '__toString')) {
            return (string) $value;
        }

        return get_class($value);
    }
}
