<?php

declare(strict_types=1);

namespace Psalm\Internal\Provider\ReturnTypeProvider;

use Override;
use Psalm\Internal\Analyzer\StatementsAnalyzer;
use Psalm\Internal\Type\Comparator\CallableTypeComparator;
use Psalm\Internal\Type\TypeCombiner;
use Psalm\Plugin\EventHandler\Event\MethodReturnTypeProviderEvent;
use Psalm\Plugin\EventHandler\MethodReturnTypeProviderInterface;
use Psalm\Type;
use Psalm\Type\Atomic\TClosure;
use Psalm\Type\Union;

/**
 * @internal
 */
final class ClosureFromCallableReturnTypeProvider implements MethodReturnTypeProviderInterface
{
    #[Override]
    public static function getClassLikeNames(): array
    {
        return ['Closure'];
    }

    #[Override]
    public static function getMethodReturnType(MethodReturnTypeProviderEvent $event): ?Union
    {
        $source = $event->getSource();
        $method_name_lowercase = $event->getMethodNameLowercase();
        $call_args = $event->getCallArgs();
        if (!$source instanceof StatementsAnalyzer) {
            return null;
        }

        $type_provider = $source->getNodeTypeProvider();
        $codebase = $source->getCodebase();
        $context = $event->getContext();

        if ($method_name_lowercase === 'fromcallable') {
            $closure_types = [];

            if (isset($call_args[0])
                && ($input_type = $type_provider->getType($call_args[0]->value))
            ) {
                foreach ($input_type->getAtomicTypes() as $atomic_type) {
                    $candidate_callable = CallableTypeComparator::getCallableFromAtomic(
                        $codebase,
                        $atomic_type,
                        null,
                        $source,
                        $context,
                        true,
                    );

                    if ($candidate_callable) {
                        $closure_types[] = new TClosure(
                            $candidate_callable->params,
                            $candidate_callable->return_type,
                            $candidate_callable->allowed_mutations,
                        );
                    } else {
                        return Type::getClosure();
                    }
                }
            }

            if ($closure_types) {
                return TypeCombiner::combine($closure_types, $codebase);
            }

            return Type::getClosure();
        }

        return null;
    }
}
