<?php

declare(strict_types=1);

namespace App\Framework\LiveComponents;

use App\Framework\DI\Container;
use App\Framework\Discovery\Results\DiscoveryRegistry;
use App\Framework\LiveComponents\Attributes\DataProvider;
use ReflectionClass;

/**
 * DataProviderResolver - Resolves DataProvider implementations
 *
 * Uses the Discovery system to find classes with #[DataProvider] attribute
 * and resolves the correct provider instance based on interface and name.
 *
 * Example:
 * ```php
 * $provider = $resolver->resolve(ChartDataProvider::class, 'demo');
 * // Returns instance of DemoChartDataProvider
 * ```
 */
final class DataProviderResolver
{
    /**
     * Cache: interface + name => provider class
     * @var array<string, class-string>
     */
    private array $cache = [];

    public function __construct(
        private readonly DiscoveryRegistry $discoveryRegistry,
        private readonly Container $container
    ) {
    }

    /**
     * Resolve a data provider by interface and name
     *
     * @template T
     * @param class-string<T> $interface The provider interface
     * @param string $name The provider name (e.g., 'demo', 'database')
     * @return T|null The resolved provider instance or null if not found
     */
    public function resolve(string $interface, string $name): ?object
    {
        error_log("[DataProviderResolver::resolve] START: interface=$interface, name=$name");

        // Check cache first
        $cacheKey = $interface . '::' . $name;
        if (isset($this->cache[$cacheKey])) {
            error_log("[DataProviderResolver::resolve] Cache HIT: $cacheKey");

            return $this->container->get($this->cache[$cacheKey]);
        }

        error_log("[DataProviderResolver::resolve] Cache MISS, calling findProviderClass");

        // Find provider class via discovery
        $providerClass = $this->findProviderClass($interface, $name);

        error_log("[DataProviderResolver::resolve] findProviderClass returned: " . ($providerClass ?? 'NULL'));

        if ($providerClass === null) {
            return null;
        }

        // Cache the result
        $this->cache[$cacheKey] = $providerClass;

        // Resolve instance from container
        return $this->container->get($providerClass);
    }

    /**
     * Find provider class that implements interface with given name
     *
     * @param class-string $interface
     * @param string $name
     * @return class-string|null
     */
    private function findProviderClass(string $interface, string $name): ?string
    {
        // Get all classes with DataProvider attribute from AttributeRegistry
        $discoveredAttributes = $this->discoveryRegistry->attributes()->get(DataProvider::class);

        // DEBUG: Write to file
        file_put_contents('/tmp/resolver-debug.log', sprintf(
            "[%s] Looking for: interface=%s, name=%s, found=%d attributes\n",
            date('Y-m-d H:i:s'),
            $interface,
            $name,
            count($discoveredAttributes)
        ), FILE_APPEND);

        foreach ($discoveredAttributes as $discoveredAttribute) {
            // Get class name as string from ClassName value object
            $className = $discoveredAttribute->className->getFullyQualified();

            file_put_contents('/tmp/resolver-debug.log', sprintf(
                "  Checking: class=%s\n",
                $className
            ), FILE_APPEND);

            // Get provider name from attribute arguments
            $arguments = $discoveredAttribute->arguments;
            if (! isset($arguments['name'])) {
                file_put_contents('/tmp/resolver-debug.log', "    SKIP: no 'name' argument\n", FILE_APPEND);

                continue; // Skip if no name argument (shouldn't happen with our attribute)
            }

            file_put_contents('/tmp/resolver-debug.log', sprintf(
                "    name='%s' (looking for '%s')\n",
                $arguments['name'],
                $name
            ), FILE_APPEND);

            // Check if name matches
            if ($arguments['name'] !== $name) {
                file_put_contents('/tmp/resolver-debug.log', "    SKIP: name mismatch\n", FILE_APPEND);

                continue;
            }

            // Check if class implements the interface
            $reflection = new ReflectionClass($className);
            $implements = $reflection->implementsInterface($interface);
            file_put_contents('/tmp/resolver-debug.log', sprintf(
                "    implements %s? %s\n",
                $interface,
                $implements ? 'YES' : 'NO'
            ), FILE_APPEND);

            if ($implements) {
                file_put_contents('/tmp/resolver-debug.log', "    MATCH! Returning $className\n", FILE_APPEND);

                return $className;
            }
        }

        return null;
    }

    /**
     * Check if a provider exists
     *
     * @param class-string $interface
     * @param string $name
     */
    public function has(string $interface, string $name): bool
    {
        $cacheKey = $interface . '::' . $name;

        if (isset($this->cache[$cacheKey])) {
            return true;
        }

        return $this->findProviderClass($interface, $name) !== null;
    }

    /**
     * Get all available provider names for an interface
     *
     * @param class-string $interface
     * @return array<string> List of provider names
     */
    public function getAvailableProviders(string $interface): array
    {
        $providers = [];

        // Get all classes with DataProvider attribute from AttributeRegistry
        $discoveredAttributes = $this->discoveryRegistry->attributes()->get(DataProvider::class);

        foreach ($discoveredAttributes as $discoveredAttribute) {
            // Get class name as string from ClassName value object
            $className = $discoveredAttribute->className->getFullyQualified();

            // Check if class implements the interface
            $reflection = new ReflectionClass($className);
            if (! $reflection->implementsInterface($interface)) {
                continue;
            }

            // Get provider name from arguments
            $arguments = $discoveredAttribute->arguments;
            if (isset($arguments['name'])) {
                $providers[] = $arguments['name'];
            }
        }

        return array_unique($providers);
    }
}
