<commit_before><?php declare(strict_types=1);

namespace GraphQL\Utils;

use GraphQL\Error\Error;
use GraphQL\Executor\Values;
use GraphQL\Language\AST\DirectiveDefinitionNode;
use GraphQL\Language\AST\EnumTypeDefinitionNode;
use GraphQL\Language\AST\EnumValueDefinitionNode;
use GraphQL\Language\AST\FieldDefinitionNode;
use GraphQL\Language\AST\InputObjectTypeDefinitionNode;
use GraphQL\Language\AST\InputValueDefinitionNode;
use GraphQL\Language\AST\InterfaceTypeDefinitionNode;
use GraphQL\Language\AST\ListTypeNode;
use GraphQL\Language\AST\NamedTypeNode;
use GraphQL\Language\AST\Node;
use GraphQL\Language\AST\NodeList;
use GraphQL\Language\AST\NonNullTypeNode;
use GraphQL\Language\AST\ObjectTypeDefinitionNode;
use GraphQL\Language\AST\ScalarTypeDefinitionNode;
use GraphQL\Language\AST\TypeDefinitionNode;
use GraphQL\Language\AST\TypeNode;
use GraphQL\Language\AST\UnionTypeDefinitionNode;
use GraphQL\Type\Definition\CustomScalarType;
use GraphQL\Type\Definition\Directive;
use GraphQL\Type\Definition\EnumType;
use GraphQL\Type\Definition\FieldDefinition;
use GraphQL\Type\Definition\InputObjectField;
use GraphQL\Type\Definition\InputObjectType;
use GraphQL\Type\Definition\InputType;
use GraphQL\Type\Definition\InterfaceType;
use GraphQL\Type\Definition\ObjectType;
use GraphQL\Type\Definition\OutputType;
use GraphQL\Type\Definition\Type;
use GraphQL\Type\Definition\UnionType;
use function is_array;
use Throwable;

/**
 * @see FieldDefinition, InputObjectField
 *
 * @phpstan-import-type UnnamedFieldDefinitionConfig from FieldDefinition
 * @phpstan-import-type InputObjectFieldConfig from InputObjectField
 * @phpstan-import-type UnnamedInputObjectFieldConfig from InputObjectField
 * @phpstan-type ResolveType callable(string, Node|null): Type
 * @phpstan-type TypeConfigDecorator callable(array<string, mixed>, Node&TypeDefinitionNode, array<string, Node&TypeDefinitionNode>): array<string, mixed>
 */
class ASTDefinitionBuilder
{
    /** @var array<string, Node&TypeDefinitionNode> */
    private array $typeDefinitionsMap;

    /**
     * @var callable
     * @phpstan-var ResolveType
     */
    private $resolveType;

    /**
     * @var callable|null
     * @phpstan-var TypeConfigDecorator|null
     */
    private $typeConfigDecorator;

    /** @var array<string, Type> */
    private array $cache;

    /**
     * @param array<string, Node&TypeDefinitionNode> $typeDefinitionsMap
     * @phpstan-param ResolveType $resolveType
     * @phpstan-param TypeConfigDecorator|null $typeConfigDecorator
     */
    public function __construct(
        array $typeDefinitionsMap,
        callable $resolveType,
        ?callable $typeConfigDecorator = null
    ) {
        $this->typeDefinitionsMap = $typeDefinitionsMap;
        $this->resolveType = $resolveType;
        $this->typeConfigDecorator = $typeConfigDecorator;

        $this->cache = Type::getAllBuiltInTypes();
    }

    public function buildDirective(DirectiveDefinitionNode $directiveNode): Directive
    {
        $locations = [];
        foreach ($directiveNode->locations as $location) {
            $locations[] = $location->value;
        }

        return new Directive([
            'name' => $directiveNode->name->value,
            'description' => $directiveNode->description->value ?? null,
            'args' => $this->makeInputValues($directiveNode->arguments),
            'isRepeatable' => $directiveNode->repeatable,
            'locations' => $locations,
            'astNode' => $directiveNode,
        ]);
    }

    /**
     * @param NodeList<InputValueDefinitionNode> $values
     *
     * @return array<string, UnnamedInputObjectFieldConfig>
     */
    private function makeInputValues(NodeList $values): array
    {
        /** @var array<string, UnnamedInputObjectFieldConfig> $map */
        $map = [];
        foreach ($values as $value) {
            // Note: While this could make assertions to get the correctly typed
            // value, that would throw immediately while type system validation
            // with validateSchema() will produce more actionable results.
            /** @var Type&InputType $type */
            $type = $this->buildWrappedType($value->type);

            $config = [
                'name' => $value->name->value,
                'type' => $type,
                'description' => $value->description->value ?? null,
                'astNode' => $value,
            ];

            if (null !== $value->defaultValue) {
                $config['defaultValue'] = AST::valueFromAST($value->defaultValue, $type);
            }

            $map[$value->name->value] = $config;
        }

        return $map;
    }

    /**
     * @param ListTypeNode|NonNullTypeNode|NamedTypeNode $typeNode
     */
    private function buildWrappedType(TypeNode $typeNode): Type
    {
        if ($typeNode instanceof ListTypeNode) {
            return Type::listOf($this->buildWrappedType($typeNode->type));
        }

        if ($typeNode instanceof NonNullTypeNode) {
            // @phpstan-ignore-next-line contained type is NullableType
            return Type::nonNull($this->buildWrappedType($typeNode->type));
        }

        return $this->buildType($typeNode);
    }

    /**
     * @param string|(Node&NamedTypeNode)|(Node&TypeDefinitionNode) $ref
     */
    public function buildType($ref): Type
    {
        if (is_string($ref)) {
            return $this->internalBuildType($ref);
        }

        return $this->internalBuildType($ref->name->value, $ref);
    }

    /**
     * Calling this method is an equivalent of `typeMap[typeName]` in `graphql-js`.
     * It is legal to access a type from the map of already-built types that doesn't exist in the map.
     * Since we build types lazily, and we don't have a such map of built types,
     * this method provides a way to build a type that may not exist in the SDL definitions and returns null instead.
     */
    public function maybeBuildType(string $name): ?Type
    {
        return isset($this->typeDefinitionsMap[$name])
            ? $this->buildType($name)
            : null
    }

    /**
     * @param (Node&NamedTypeNode)|(Node&TypeDefinitionNode)|null $typeNode
     *
     * @throws Error
     */
    private function internalBuildType(string $typeName, ?Node $typeNode = null): Type
    {
        if (isset($this->cache[$typeName])) {
            return $this->cache[$typeName];
        }

        if (isset($this->typeDefinitionsMap[$typeName])) {
            $type = $this->makeSchemaDef($this->typeDefinitionsMap[$typeName]);

            if (isset($this->typeConfigDecorator)) {<commit_msg>fix syntax<commit_after><?php declare(strict_types=1);

namespace GraphQL\Utils;

use GraphQL\Error\Error;
use GraphQL\Executor\Values;
use GraphQL\Language\AST\DirectiveDefinitionNode;
use GraphQL\Language\AST\EnumTypeDefinitionNode;
use GraphQL\Language\AST\EnumValueDefinitionNode;
use GraphQL\Language\AST\FieldDefinitionNode;
use GraphQL\Language\AST\InputObjectTypeDefinitionNode;
use GraphQL\Language\AST\InputValueDefinitionNode;
use GraphQL\Language\AST\InterfaceTypeDefinitionNode;
use GraphQL\Language\AST\ListTypeNode;
use GraphQL\Language\AST\NamedTypeNode;
use GraphQL\Language\AST\Node;
use GraphQL\Language\AST\NodeList;
use GraphQL\Language\AST\NonNullTypeNode;
use GraphQL\Language\AST\ObjectTypeDefinitionNode;
use GraphQL\Language\AST\ScalarTypeDefinitionNode;
use GraphQL\Language\AST\TypeDefinitionNode;
use GraphQL\Language\AST\TypeNode;
use GraphQL\Language\AST\UnionTypeDefinitionNode;
use GraphQL\Type\Definition\CustomScalarType;
use GraphQL\Type\Definition\Directive;
use GraphQL\Type\Definition\EnumType;
use GraphQL\Type\Definition\FieldDefinition;
use GraphQL\Type\Definition\InputObjectField;
use GraphQL\Type\Definition\InputObjectType;
use GraphQL\Type\Definition\InputType;
use GraphQL\Type\Definition\InterfaceType;
use GraphQL\Type\Definition\ObjectType;
use GraphQL\Type\Definition\OutputType;
use GraphQL\Type\Definition\Type;
use GraphQL\Type\Definition\UnionType;
use function is_array;
use Throwable;

/**
 * @see FieldDefinition, InputObjectField
 *
 * @phpstan-import-type UnnamedFieldDefinitionConfig from FieldDefinition
 * @phpstan-import-type InputObjectFieldConfig from InputObjectField
 * @phpstan-import-type UnnamedInputObjectFieldConfig from InputObjectField
 * @phpstan-type ResolveType callable(string, Node|null): Type
 * @phpstan-type TypeConfigDecorator callable(array<string, mixed>, Node&TypeDefinitionNode, array<string, Node&TypeDefinitionNode>): array<string, mixed>
 */
class ASTDefinitionBuilder
{
    /** @var array<string, Node&TypeDefinitionNode> */
    private array $typeDefinitionsMap;

    /**
     * @var callable
     * @phpstan-var ResolveType
     */
    private $resolveType;

    /**
     * @var callable|null
     * @phpstan-var TypeConfigDecorator|null
     */
    private $typeConfigDecorator;

    /** @var array<string, Type> */
    private array $cache;

    /**
     * @param array<string, Node&TypeDefinitionNode> $typeDefinitionsMap
     * @phpstan-param ResolveType $resolveType
     * @phpstan-param TypeConfigDecorator|null $typeConfigDecorator
     */
    public function __construct(
        array $typeDefinitionsMap,
        callable $resolveType,
        ?callable $typeConfigDecorator = null
    ) {
        $this->typeDefinitionsMap = $typeDefinitionsMap;
        $this->resolveType = $resolveType;
        $this->typeConfigDecorator = $typeConfigDecorator;

        $this->cache = Type::getAllBuiltInTypes();
    }

    public function buildDirective(DirectiveDefinitionNode $directiveNode): Directive
    {
        $locations = [];
        foreach ($directiveNode->locations as $location) {
            $locations[] = $location->value;
        }

        return new Directive([
            'name' => $directiveNode->name->value,
            'description' => $directiveNode->description->value ?? null,
            'args' => $this->makeInputValues($directiveNode->arguments),
            'isRepeatable' => $directiveNode->repeatable,
            'locations' => $locations,
            'astNode' => $directiveNode,
        ]);
    }

    /**
     * @param NodeList<InputValueDefinitionNode> $values
     *
     * @return array<string, UnnamedInputObjectFieldConfig>
     */
    private function makeInputValues(NodeList $values): array
    {
        /** @var array<string, UnnamedInputObjectFieldConfig> $map */
        $map = [];
        foreach ($values as $value) {
            // Note: While this could make assertions to get the correctly typed
            // value, that would throw immediately while type system validation
            // with validateSchema() will produce more actionable results.
            /** @var Type&InputType $type */
            $type = $this->buildWrappedType($value->type);

            $config = [
                'name' => $value->name->value,
                'type' => $type,
                'description' => $value->description->value ?? null,
                'astNode' => $value,
            ];

            if (null !== $value->defaultValue) {
                $config['defaultValue'] = AST::valueFromAST($value->defaultValue, $type);
            }

            $map[$value->name->value] = $config;
        }

        return $map;
    }

    /**
     * @param ListTypeNode|NonNullTypeNode|NamedTypeNode $typeNode
     */
    private function buildWrappedType(TypeNode $typeNode): Type
    {
        if ($typeNode instanceof ListTypeNode) {
            return Type::listOf($this->buildWrappedType($typeNode->type));
        }

        if ($typeNode instanceof NonNullTypeNode) {
            // @phpstan-ignore-next-line contained type is NullableType
            return Type::nonNull($this->buildWrappedType($typeNode->type));
        }

        return $this->buildType($typeNode);
    }

    /**
     * @param string|(Node&NamedTypeNode)|(Node&TypeDefinitionNode) $ref
     */
    public function buildType($ref): Type
    {
        if (is_string($ref)) {
            return $this->internalBuildType($ref);
        }

        return $this->internalBuildType($ref->name->value, $ref);
    }

    /**
     * Calling this method is an equivalent of `typeMap[typeName]` in `graphql-js`.
     * It is legal to access a type from the map of already-built types that doesn't exist in the map.
     * Since we build types lazily, and we don't have a such map of built types,
     * this method provides a way to build a type that may not exist in the SDL definitions and returns null instead.
     */
    public function maybeBuildType(string $name): ?Type
    {
        return isset($this->typeDefinitionsMap[$name])
            ? $this->buildType($name)
            : null;
    }

    /**
     * @param (Node&NamedTypeNode)|(Node&TypeDefinitionNode)|null $typeNode
     *
     * @throws Error
     */
    private function internalBuildType(string $typeName, ?Node $typeNode = null): Type
    {
        if (isset($this->cache[$typeName])) {
            return $this->cache[$typeName];
        }

        if (isset($this->typeDefinitionsMap[$typeName])) {
            $type = $this->makeSchemaDef($this->typeDefinitionsMap[$typeName]);

            if (isset($this->typeConfigDecorator)) {
{'fraction_non_alphanumeric': 0.11655839250642092, 'fraction_numerical': 0.00015108022359873092, 'mean_word_length': 3.0685310387215736, 'pattern_counts': {'":': 0, '<': 23, '<?xml version=': 0, '>': 121, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '5609933', 'n_tokens_mistral': 3743, 'n_tokens_neox': 3662, 'n_words': 785}