<?php

declare(strict_types=1);

namespace App\Framework\DI;

use App\Framework\DI\Attributes\DefaultImplementation;
use App\Framework\DI\Exceptions\DefaultImplementationException;
use App\Framework\Discovery\Results\DiscoveryRegistry;
use App\Framework\Discovery\ValueObjects\DiscoveredAttribute;
use ReflectionClass;

/**
 * Processes DefaultImplementation attributes and registers bindings in the DI container
 *
 * This processor:
 * 1. Discovers classes with #[DefaultImplementation] attribute
 * 2. Validates interface implementation
 * 3. Auto-detects interface if not explicitly specified
 * 4. Registers bindings in the container
 */
final readonly class DefaultImplementationProcessor
{
    public function __construct(
        private Container $container
    ) {}

    /**
     * Process all DefaultImplementation attributes from discovery results
     *
     * @param DiscoveryRegistry $registry
     * @return int Number of bindings registered
     */
    public function process(DiscoveryRegistry $registry): int
    {
        $attributes = $registry->attributes->get(DefaultImplementation::class);

        if ($attributes === null) {
            return 0;
        }

        $registered = 0;

        foreach ($attributes as $discoveredAttribute) {
            try {
                $this->processAttribute($discoveredAttribute);
                $registered++;
            } catch (DefaultImplementationException $e) {
                // Re-throw validation exceptions - these should be caught during development
                throw $e;
            }
        }

        return $registered;
    }

    /**
     * Process a single DefaultImplementation attribute
     *
     * @param DiscoveredAttribute $discovered
     * @throws DefaultImplementationException
     */
    private function processAttribute(DiscoveredAttribute $discovered): void
    {
        $className = $discovered->className->getFullyQualified();

        // Create the attribute instance to access its properties
        $attribute = $discovered->createAttributeInstance();

        if (!($attribute instanceof DefaultImplementation)) {
            return;
        }

        // Determine the interface to bind to
        $interface = $this->resolveInterface($className, $attribute->interface);

        // Register binding in container
        $this->container->singleton($interface, $className);
    }

    /**
     * Resolve the interface for binding
     *
     * If interface is explicitly provided, validate it.
     * Otherwise, auto-detect the first interface the class implements.
     *
     * @param class-string $className
     * @param class-string|null $explicitInterface
     * @return class-string
     * @throws DefaultImplementationException
     */
    private function resolveInterface(string $className, ?string $explicitInterface): string
    {
        if ($explicitInterface !== null) {
            return $this->validateExplicitInterface($className, $explicitInterface);
        }

        return $this->autoDetectInterface($className);
    }

    /**
     * Validate that the class implements the explicitly specified interface
     *
     * @param class-string $className
     * @param class-string $interface
     * @return class-string
     * @throws DefaultImplementationException
     */
    private function validateExplicitInterface(string $className, string $interface): string
    {
        // Check if interface exists
        if (!interface_exists($interface)) {
            throw DefaultImplementationException::interfaceDoesNotExist($className, $interface);
        }

        // Check if class implements the interface
        $reflection = new ReflectionClass($className);
        if (!$reflection->implementsInterface($interface)) {
            throw DefaultImplementationException::doesNotImplementInterface($className, $interface);
        }

        return $interface;
    }

    /**
     * Auto-detect the first interface the class implements
     *
     * @param class-string $className
     * @return class-string
     * @throws DefaultImplementationException
     */
    private function autoDetectInterface(string $className): string
    {
        $reflection = new ReflectionClass($className);
        $interfaces = $reflection->getInterfaceNames();

        if (empty($interfaces)) {
            throw DefaultImplementationException::noInterfacesImplemented($className);
        }

        // Return the first interface
        return $interfaces[0];
    }
}
