text: using System;
  using System.Collections.Generic;
  using System.Linq;
  using HotChocolate.Language;
  using HotChocolate.Types;
  using static HotChocolate.Execution.ThrowHelper;
  
  namespace HotChocolate.Execution.Batching
  {
      internal class CollectVariablesVisitor
          : ISyntaxNodeVisitor<OperationDefinitionNode>
          , ISyntaxNodeVisitor<FieldNode>
          , ISyntaxNodeVisitor<ArgumentNode>
          , ISyntaxNodeVisitor<ObjectFieldNode>
          , ISyntaxNodeVisitor<VariableNode>
          , ISyntaxNodeVisitor<InlineFragmentNode>
          , ISyntaxNodeVisitor<FragmentSpreadNode>
          , ISyntaxNodeVisitor<FragmentDefinitionNode>
      {
          private readonly Dictionary<string, VariableDefinitionNode> _variables =
              new Dictionary<string, VariableDefinitionNode>();
          private readonly HashSet<string> _declared = new HashSet<string>();
          private readonly Stack<IType> _type = new Stack<IType>();
          private readonly Stack<IOutputField> _field =
              new Stack<IOutputField>();
          private readonly Stack<VisitorAction> _action =
              new Stack<VisitorAction>();
          private readonly HashSet<string> _touchedFragments =
              new HashSet<string>();
          private readonly ISchema _schema;
  
          public CollectVariablesVisitor(ISchema schema)
          {
              _schema = schema ?? throw new ArgumentNullException(nameof(schema));
          }
  
          public IReadOnlyCollection<VariableDefinitionNode>
              VariableDeclarations =>
                  _variables.Values;
          public IReadOnlyCollection<string> TouchedFragments =>
              _touchedFragments;
  
          public VisitorAction Enter(
              OperationDefinitionNode node,
              ISyntaxNode parent,
              IReadOnlyList<object> path,
              IReadOnlyList<ISyntaxNode> ancestors)
          {
              _variables.Clear();
              _field.Clear();
              _type.Clear();
              _action.Clear();
              _touchedFragments.Clear();
  
              ObjectType type = _schema.GetOperationType(node.Operation);
              _type.Push(type);
  
              for (var i = 0; i < node.VariableDefinitions.Count; i++)
              {
                  _declared.Add(
                      node.VariableDefinitions[i].Variable.Name.Value);
              }
  
              return VisitorAction.Continue;
          }
  
          public VisitorAction Leave(
              OperationDefinitionNode node,
              ISyntaxNode parent,
              IReadOnlyList<object> path,
              IReadOnlyList<ISyntaxNode> ancestors)
          {
              _type.Pop();
              return VisitorAction.Continue;
          }
  
          public VisitorAction Enter(
              FieldNode node,
              ISyntaxNode parent,
              IReadOnlyList<object> path,
              IReadOnlyList<ISyntaxNode> ancestors)
          {
              if (_type.Peek().NamedType() is IComplexOutputType complexType
                  && complexType.Fields.TryGetField(
                      node.Name.Value, out IOutputField? field))
              {
                  _field.Push(field);
                  _type.Push(field.Type);
                  _action.Push(VisitorAction.Continue);
                  return VisitorAction.Continue;
              }
  
              _action.Push(VisitorAction.Skip);
              return VisitorAction.Skip;
          }
  
          public VisitorAction Leave(
              FieldNode node,
              ISyntaxNode parent,
              IReadOnlyList<object> path,
              IReadOnlyList<ISyntaxNode> ancestors)
          {
              if (_action.Pop() == VisitorAction.Continue)
              {
                  _type.Pop();
                  _field.Pop();
              }
              return VisitorAction.Continue;
          }
  
          public VisitorAction Enter(
              ArgumentNode node,
              ISyntaxNode parent,
              IReadOnlyList<object> path,
              IReadOnlyList<ISyntaxNode> ancestors)
          {
              if (_field.Peek().Arguments.TryGetField(
                  node.Name.Value,
                  out IInputField? field))
              {
                  _type.Push(field.Type);
                  _action.Push(VisitorAction.Continue);
                  return VisitorAction.Continue;
              }
  
              _action.Push(VisitorAction.Skip);
              return VisitorAction.Skip;
          }
  
          public VisitorAction Leave(
              ArgumentNode node,
              ISyntaxNode parent,
              IReadOnlyList<object> path,
              IReadOnlyList<ISyntaxNode> ancestors)
          {
              if (_action.Pop() == VisitorAction.Continue)
              {
                  _type.Pop();
              }
              return VisitorAction.Continue;
          }
  
          public VisitorAction Enter(
              ObjectFieldNode node,
              ISyntaxNode parent,
              IReadOnlyList<object> path,
              IReadOnlyList<ISyntaxNode> ancestors)
          {
              if (_type.Peek().NamedType() is InputObjectType inputObject
                  && inputObject.Fields.TryGetField(
                      node.Name.Value,
                      out IInputField? field))
              {
                  _type.Push(field.Type);
                  _action.Push(VisitorAction.Continue);
                  return VisitorAction.Continue;
              }
  
              _action.Push(VisitorAction.Skip);
              return VisitorAction.Skip;
          }
  
          public VisitorAction Leave(
              ObjectFieldNode node,
              ISyntaxNode parent,
              IReadOnlyList<object> path,
              IReadOnlyList<ISyntaxNode> ancestors)
          {
              if (_action.Pop() == VisitorAction.Continue)
              {
                  _type.Pop();
              }
              return VisitorAction.Continue;
          }
  
          public VisitorAction Enter(
              VariableNode node,
              ISyntaxNode parent,
              IReadOnlyList<object> path,
              IReadOnlyList<ISyntaxNode> ancestors)
          {
              if (!_declared.Contains(node.Name.Value))
              {
                  IType type = _type.Peek();
  
                  if (_variables.TryGetValue(
                      node.Name.Value,
                      out VariableDefinitionNode? d))
                  {
                      if (type.IsNonNullType()
                          && d.Type is INullableTypeNode nullable)
                      {
                          _variables[node.Name.Value] =
                              d.WithType(new NonNullTypeNode(nullable));
                      }
                  }
                  else
                  {
                      if (type.NamedType() is IComplexOutputType complexType)
                      {
                          Type clrType = complexType.ToRuntimeType();
                          InputObjectType inputType =
                              _schema.Types.OfType<InputObjectType>()
                                  .First(t => t.RuntimeType == clrType);
  
                          if (inputType is null)
                          {
                              throw CollectVariablesVisitor_NoCompatibleType(node, path);
                          }
  
                          d = new VariableDefinitionNode
                          (
                              null,
                              node,
                              type.ToTypeNode(inputType),
                              null,
                              Array.Empty<DirectiveNode>()
                          );
                      }
                      else
                      {
                          d = new VariableDefinitionNode
                          (
                              null,
                              node,
                              type.ToTypeNode(),
                              null,
                              Array.Empty<DirectiveNode>()
                          );
                      }
  
                      _variables[node.Name.Value] = d;
                  }
              }
  
              return VisitorAction.Continue;
          }
  
          public VisitorAction Leave(
              VariableNode node,
              ISyntaxNode parent,
              IReadOnlyList<object> path,
              IReadOnlyList<ISyntaxNode> ancestors)
          {
              return VisitorAction.Skip;
          }
  
          public VisitorAction Enter(
              InlineFragmentNode node,
              ISyntaxNode parent,
              IReadOnlyList<object> path,
              IReadOnlyList<ISyntaxNode> ancestors)
          {
              if (node.TypeCondition is not null && 
                  _schema.TryGetType(
                      node.TypeCondition.Name.Value,
                      out INamedType? type))
              {
                  _type.Push(type);
                  _action.Push(VisitorAction.Continue);
                  return VisitorAction.Continue;
              }
  
              _action.Push(VisitorAction.Skip);
              return VisitorAction.Skip;
          }
  
          public VisitorAction Leave(
              InlineFragmentNode node,
              ISyntaxNode parent,
              IReadOnlyList<object> path,
              IReadOnlyList<ISyntaxNode> ancestors)
          {
              if (_action.Pop() == VisitorAction.Continue)
              {
                  _type.Pop();
              }
              return VisitorAction.Continue;
          }
  
          public VisitorAction Enter(
              FragmentDefinitionNode node,
              ISyntaxNode parent,
              IReadOnlyList<object> path,
              IReadOnlyList<ISyntaxNode> ancestors)
          {
              if (_schema.TryGetType(
                  node.TypeCondition.Name.Value,
                  out INamedType type))
              {
                  _touchedFragments.Add(node.Name.Value);
                  _type.Push(type);
                  _action.Push(VisitorAction.Continue);
                  return VisitorAction.Continue;
              }
  
              _action.Push(VisitorAction.Skip);
              return VisitorAction.Skip;
          }
  
          public VisitorAction Leave(
              FragmentDefinitionNode node,
              ISyntaxNode parent,
              IReadOnlyList<object> path,
              IReadOnlyList<ISyntaxNode> ancestors)
          {
              if (_action.Pop() == VisitorAction.Continue)
              {
                  _type.Pop();
              }
              return VisitorAction.Continue;
          }
  
          public VisitorAction Enter(
              FragmentSpreadNode node,
              ISyntaxNode parent,
              IReadOnlyList<object> path,
              IReadOnlyList<ISyntaxNode> ancestors)
          {
              return VisitorAction.Continue;
          }
  
          public VisitorAction Leave(
              FragmentSpreadNode node,
              ISyntaxNode parent,
              IReadOnlyList<object> path,
              IReadOnlyList<ISyntaxNode> ancestors)
          {
              return VisitorAction.Continue;
          }
      }
  }
  