<?php

namespace MintyPHP\Console;

/**
 * Minimal CLI command dispatcher with auto-discovery.
 *
 * Parses argv into command name, positional args, and --options,
 * then dispatches to the matching registered Command instance.
 */
final class ConsoleApplication
{
    /** @var array<string, Command> */
    private array $commands = [];

    public function register(Command $command): void
    {
        $this->commands[$command->name()] = $command;
    }

    /**
     * Auto-discover and register all Command subclasses in a directory.
     *
     * Scans PHP files recursively, extracts the FQCN from namespace + class name,
     * and registers any concrete Command subclass found.
     */
    public function discoverCommands(string $directory): void
    {
        if (!is_dir($directory)) {
            return;
        }

        $iterator = new \RecursiveIteratorIterator(
            new \RecursiveDirectoryIterator($directory, \FilesystemIterator::SKIP_DOTS),
            \RecursiveIteratorIterator::LEAVES_ONLY
        );

        foreach ($iterator as $file) {
            if ($file->getExtension() !== 'php') {
                continue;
            }

            $content = file_get_contents($file->getPathname());
            if ($content === false) {
                continue;
            }

            // Extract namespace and class name from file
            $namespace = '';
            if (preg_match('/^namespace\s+(.+?);/m', $content, $nsMatch)) {
                $namespace = $nsMatch[1];
            }

            $className = '';
            if (preg_match('/^(?:final\s+|abstract\s+)?class\s+(\w+)/m', $content, $classMatch)) {
                $className = $classMatch[1];
            }

            if ($className === '' || $namespace === '') {
                continue;
            }

            $fqcn = $namespace . '\\' . $className;

            if (!class_exists($fqcn)) {
                continue;
            }

            $reflection = new \ReflectionClass($fqcn);
            if ($reflection->isAbstract() || !$reflection->isSubclassOf(Command::class)) {
                continue;
            }

            $this->register($reflection->newInstance());
        }
    }

    /**
     * @param list<string> $argv Raw CLI arguments (including script name at [0])
     */
    public function run(array $argv): int
    {
        $commandName = $argv[1] ?? '';

        if ($commandName === '' || $commandName === 'list' || $commandName === '--help') {
            $this->printCommandList();
            return 0;
        }

        if (!isset($this->commands[$commandName])) {
            fwrite(STDERR, "Unknown command: {$commandName}\n\n");
            $this->printCommandList();
            return 1;
        }

        [$args, $options] = self::parseArgv(array_slice($argv, 2));

        // Per-command --help
        if ($options['help'] ?? false) {
            $command = $this->commands[$commandName];
            $usage = $command->usage();
            if ($usage !== '') {
                fwrite(STDOUT, $usage . "\n");
            } else {
                fwrite(STDOUT, sprintf("%s — %s\n", $command->name(), $command->description()));
            }
            return 0;
        }

        return $this->commands[$commandName]->execute($args, $options);
    }

    private function printCommandList(): void
    {
        fwrite(STDOUT, "Usage: php bin/console <command> [arguments] [options]\n\n");
        fwrite(STDOUT, "Available commands:\n");

        // Group by namespace prefix
        $grouped = [];
        foreach ($this->commands as $name => $command) {
            $parts = explode(':', $name, 2);
            $group = count($parts) === 2 ? $parts[0] : '';
            $grouped[$group][$name] = $command->description();
        }
        ksort($grouped);

        foreach ($grouped as $group => $commands) {
            if ($group !== '') {
                fwrite(STDOUT, " {$group}\n");
            }
            ksort($commands);
            foreach ($commands as $name => $description) {
                fwrite(STDOUT, sprintf("  %-30s %s\n", $name, $description));
            }
        }
    }

    /**
     * @param list<string> $tokens
     * @return array{list<string>, array<string, string|bool>}
     */
    private static function parseArgv(array $tokens): array
    {
        $args = [];
        $options = [];

        foreach ($tokens as $token) {
            if (str_starts_with($token, '--')) {
                $option = substr($token, 2);
                if (str_contains($option, '=')) {
                    [$key, $value] = explode('=', $option, 2);
                    $options[$key] = $value;
                } else {
                    $options[$option] = true;
                }
            } else {
                $args[] = $token;
            }
        }

        return [$args, $options];
    }
}
