<?php

declare(strict_types=1);

namespace App\Framework\DI\Exceptions;

use App\Framework\Exception\ExceptionContext;

/**
 * Thrown when a class cannot be resolved due to reflection or other errors
 */
final class ClassResolutionException extends ContainerException
{
    /**
     * Factory method to create exception from binding resolution failure
     *
     * @param class-string $class
     * @param \Throwable $previous
     * @param string[] $availableBindings
     * @param string[] $dependencyChain
     * @param string $bindingType One of 'callable', 'string', or 'object'
     */
    public static function fromBindingResolution(
        string $class,
        \Throwable $previous,
        array $availableBindings,
        array $dependencyChain,
        string $bindingType
    ): self {
        $reason = "Binding resolution failed (binding type: {$bindingType}): " . $previous->getMessage();

        return new self(
            class: $class,
            reason: $reason,
            availableBindings: $availableBindings,
            dependencyChain: $dependencyChain,
            previous: $previous,
            bindingType: $bindingType
        );
    }

    /**
     * Factory method to create exception from RuntimeException or ReflectionException
     *
     * @param class-string $class
     * @param \RuntimeException|\ReflectionException $exception
     * @param string[] $availableBindings
     * @param string[] $dependencyChain
     */
    public static function fromRuntimeException(
        string $class,
        \RuntimeException|\ReflectionException $exception,
        array $availableBindings,
        array $dependencyChain
    ): self {
        return new self(
            class: $class,
            reason: $exception->getMessage(),
            availableBindings: $availableBindings,
            dependencyChain: $dependencyChain,
            previous: $exception
        );
    }

    /**
     * @param string[] $dependencyChain
     * @param string[] $availableBindings
     */
    public function __construct(
        string $class,
        string $reason,
        array $availableBindings,
        array $dependencyChain,
        int $code = 0,
        ?\Throwable $previous = null,
        ?string $bindingType = null
    ) {
        $message = "Cannot resolve class '{$class}': {$reason}. " .
            "Available bindings: " . implode(', ', $availableBindings) .
            ". Dependency chain: " . implode(' -> ', $dependencyChain);

        $contextData = [
            'class' => $class,
            'reason' => $reason,
            'availableBindings' => $availableBindings,
            'dependencyChain' => $dependencyChain,
            'bindingCount' => count($availableBindings),
        ];

        if ($bindingType !== null) {
            $contextData['bindingType'] = $bindingType;
        }

        $context = ExceptionContext::forOperation('class_resolution', 'DI Container')
            ->withData($contextData);

        parent::__construct(
            message: $message,
            context: $context,
            code: $code,
            previous: $previous
        );
    }
}

