<?php

/**
 * Aphiria
 *
 * @link      https://www.aphiria.com
 * @copyright Copyright (C) 2025 David Young
 * @license   https://github.com/aphiria/aphiria/blob/1.x/LICENSE.md
 */

declare(strict_types=1);

namespace Aphiria\Framework\Tests\Console\Components;

use Aphiria\Console\Commands\Attributes\AttributeCommandRegistrant;
use Aphiria\Console\Commands\Command;
use Aphiria\Console\Commands\CommandRegistrantCollection;
use Aphiria\Console\Commands\CommandRegistry;
use Aphiria\Console\Commands\ICommandHandler;
use Aphiria\Console\Input\Input;
use Aphiria\Console\Output\Compilers\Elements\Element;
use Aphiria\Console\Output\Compilers\Elements\ElementRegistry;
use Aphiria\Console\Output\Compilers\Elements\Style;
use Aphiria\Console\Output\IOutput;
use Aphiria\DependencyInjection\Container;
use Aphiria\Framework\Console\Components\CommandComponent;
use PHPUnit\Framework\TestCase;
use RuntimeException;

class CommandComponentTest extends TestCase
{
    private CommandComponent $commandComponent;
    private CommandRegistrantCollection $commandRegistrants;
    private CommandRegistry $commands;
    private Container $container;

    protected function setUp(): void
    {
        // Using a real container to simplify testing
        $this->container = new Container();
        $this->commandComponent = new CommandComponent($this->container);

        $this->container->bindInstance(CommandRegistry::class, $this->commands = new CommandRegistry());
        $this->container->bindInstance(CommandRegistrantCollection::class, $this->commandRegistrants = new class () extends CommandRegistrantCollection {
            public array $values {
                get => $this->commandRegistrants;
            }
        });
    }

    public function testBuildRegistersCommandsRegisteredInCallbacks(): void
    {
        $expectedCommand = new Command('foo');
        $commandHandler = new class () implements ICommandHandler {
            public function handle(Input $input, IOutput $output): void {}
        };
        $this->commandComponent->withCommands(fn(CommandRegistry $commands) => $commands->registerCommand($expectedCommand, $commandHandler::class));
        $this->commandComponent->build();
        $this->assertCount(1, $this->commands->commandBindings);
        $this->assertSame($expectedCommand, $this->commands->commandBindings[0]->command);
        $this->assertSame($commandHandler::class, $this->commands->commandBindings[0]->commandHandlerClassName);
    }

    public function testBuildWithAttributesAddsAttributeRegistrant(): void
    {
        $attributeCommandRegistrant = new AttributeCommandRegistrant(__DIR__);
        $this->container->bindInstance(AttributeCommandRegistrant::class, $attributeCommandRegistrant);
        $this->commandComponent->withAttributes();
        $this->commandComponent->build();
        // We should have two - one for attributes and another for manually-registered commands
        $this->assertCount(2, $this->commandRegistrants->values);
        // Make sure that attributes are registered first
        $this->assertEquals($attributeCommandRegistrant, $this->commandRegistrants->values[0]);
    }

    public function testBuildWithAttributesWithoutAttributeRegistrantThrowsException(): void
    {
        $this->expectException(RuntimeException::class);
        $this->expectExceptionMessage(AttributeCommandRegistrant::class . ' cannot be null if using attributes');
        $this->commandComponent->withAttributes();
        $this->commandComponent->build();
    }

    public function testBuildWithMultipleElementRegistersItToElementRegistry(): void
    {
        $elements = new ElementRegistry();
        $this->container->bindInstance(ElementRegistry::class, $elements);
        $element1 = new Element('foo', new Style());
        $element2 = new Element('bar', new Style());
        $this->commandComponent->withElement([$element1, $element2]);
        $this->commandComponent->build();
        $this->assertSame($element1, $elements->getElement('foo'));
        $this->assertSame($element2, $elements->getElement('bar'));
    }

    public function testBuildWithSingleElementRegistersItToElementRegistry(): void
    {
        $elements = new ElementRegistry();
        $this->container->bindInstance(ElementRegistry::class, $elements);
        $element = new Element('foo', new Style());
        $this->commandComponent->withElement($element);
        $this->commandComponent->build();
        $this->assertSame($element, $elements->getElement('foo'));
    }
}
