<?php

declare(strict_types=1);

namespace App\Framework\Console;

use App\Framework\Exception\Core\ConsoleErrorCode;
use App\Framework\Exception\FrameworkException;
use ArrayIterator;
use Countable;
use IteratorAggregate;
use Traversable;

/**
 * Value Object für die Verwaltung von Console Commands
 * @implements IteratorAggregate<string, ConsoleCommand>
 */
final readonly class CommandList implements IteratorAggregate, Countable
{
    /** @var array<string, ConsoleCommand> */
    private array $commands;

    public function __construct(ConsoleCommand ...$commands)
    {
        $commandMap = [];

        foreach ($commands as $command) {
            if (isset($commandMap[$command->name])) {
                throw FrameworkException::create(
                    ConsoleErrorCode::INVALID_COMMAND_STRUCTURE,
                    "Duplicate command name '{$command->name}'"
                )->withData(['command_name' => $command->name]);
            }

            $commandMap[$command->name] = $command;
        }

        $this->commands = $commandMap;
    }

    public static function empty(): self
    {
        return new self();
    }

    public function add(ConsoleCommand $command): self
    {
        if ($this->has($command->name)) {
            throw FrameworkException::create(
                ConsoleErrorCode::INVALID_COMMAND_STRUCTURE,
                "Command '{$command->name}' already exists"
            )->withData(['command_name' => $command->name]);
        }

        $allCommands = array_values($this->commands);
        $allCommands[] = $command;

        return new self(...$allCommands);
    }

    public function has(string $name): bool
    {
        return isset($this->commands[$name]);
    }

    public function get(string $name): ConsoleCommand
    {
        if (! $this->has($name)) {
            throw FrameworkException::create(
                ConsoleErrorCode::COMMAND_NOT_FOUND,
                "Command '{$name}' not found"
            )->withData(['command_name' => $name]);
        }

        return $this->commands[$name];
    }

    /**
     * @return array<int, string>
     */
    public function getNames(): array
    {
        return array_keys($this->commands);
    }

    /**
     * @return array<int, string>
     */
    public function findSimilar(string $name, int $maxDistance = 3): array
    {
        $suggestions = [];

        foreach ($this->getNames() as $commandName) {
            $distance = levenshtein($name, $commandName);
            if ($distance <= $maxDistance && $distance > 0) {
                $suggestions[] = $commandName;
            }
        }

        return $suggestions;
    }

    public function count(): int
    {
        return count($this->commands);
    }

    public function getIterator(): Traversable
    {
        return new ArrayIterator($this->commands);
    }

    /**
     * @return array<string, ConsoleCommand>
     */
    public function toArray(): array
    {
        return $this->commands;
    }

    /**
     * @return array<int, ConsoleCommand>
     */
    public function getAllCommands(): array
    {
        return array_values($this->commands);
    }

    public function isEmpty(): bool
    {
        return empty($this->commands);
    }
}
