// Path: src/main/java/com/nfl/glitr/exception/GlitrException.java
// public class GlitrException extends RuntimeException {
// 
//     public GlitrException(String message) {
//         super(message);
//     }
// 
//     public GlitrException(String message, Throwable cause) {
//         super(message, cause);
//     }
// }
// 
// Path: src/main/java/com/nfl/glitr/registry/schema/GlitrFieldDefinition.java
// public class GlitrFieldDefinition extends FieldDefinition {
// 
//     private Set<GlitrMetaDefinition> metaDefinitions = new HashSet<>();
// 
// 
//     public GlitrFieldDefinition(String name, Type type) {
//         super(name, type);
//     }
// 
//     public GlitrFieldDefinition(String name, Set<GlitrMetaDefinition> metaDefinitions) {
//         super(name, null);
//         this.metaDefinitions = metaDefinitions;
//     }
// 
//     public GlitrFieldDefinition(String name, Type type, Set<GlitrMetaDefinition> metaDefinitions) {
//         super(name, type);
//         this.metaDefinitions = metaDefinitions;
//     }
// 
//     public Set<GlitrMetaDefinition> getMetaDefinitions() {
//         return metaDefinitions;
//     }
// 
//     public void setMetaDefinitions(Set<GlitrMetaDefinition> metaDefinitions) {
//         this.metaDefinitions = metaDefinitions;
//     }
// 
//     public void addMetaDefinitions(GlitrMetaDefinition metaDefinition) {
//         if (this.metaDefinitions == null) {
//             this.metaDefinitions = new HashSet<>();
//         }
//         this.metaDefinitions.add(metaDefinition);
//     }
// }
// 
// Path: src/main/java/com/nfl/glitr/registry/schema/GlitrMetaDefinition.java
// public class GlitrMetaDefinition {
// 
//     private String name;
//     private Object value;
// 
// 
//     public GlitrMetaDefinition(String name, Object value) {
//         this.name = name;
//         this.value = value;
//     }
// 
//     public String getName() {
//         return name;
//     }
// 
//     public void setName(String name) {
//         this.name = name;
//     }
// 
//     public Object getValue() {
//         return value;
//     }
// 
//     public void setValue(Object value) {
//         this.value = value;
//     }
// 
//     @Override
//     public boolean equals(Object o) {
//         if (this == o) return true;
// 
//         if (o == null || getClass() != o.getClass()) return false;
// 
//         GlitrMetaDefinition that = (GlitrMetaDefinition) o;
// 
//         return new EqualsBuilder()
//                 .append(name, that.name)
//                 .isEquals();
//     }
// 
//     @Override
//     public int hashCode() {
//         return new HashCodeBuilder(17, 37)
//                 .append(name)
//                 .toHashCode();
//     }
// }
// 
// Path: src/main/java/com/nfl/glitr/registry/schema/GraphQLConnectionList.java
// public class GraphQLConnectionList extends GraphQLList {
//     public GraphQLConnectionList(GraphQLType wrappedType) {
//         super(wrappedType);
//     }
// }
// 
// Path: src/main/java/com/nfl/glitr/util/NodeUtil.java
// public static final String COMPLEXITY_FORMULA_KEY = "complexity_formula";
// 
// Path: src/main/java/com/nfl/glitr/util/NodeUtil.java
// public static final String COMPLEXITY_IGNORE_KEY = "complexity_ignore";


import com.nfl.glitr.exception.GlitrException;
import com.nfl.glitr.registry.schema.GlitrFieldDefinition;
import com.nfl.glitr.registry.schema.GlitrMetaDefinition;
import com.nfl.glitr.registry.schema.GraphQLConnectionList;
import graphql.language.*;
import graphql.parser.InvalidSyntaxException;
import graphql.parser.Parser;
import graphql.parser.antlr.GraphqlLexer;
import graphql.schema.*;
import net.objecthunter.exp4j.Expression;
import net.objecthunter.exp4j.ExpressionBuilder;
import org.antlr.v4.runtime.CharStreams;
import org.antlr.v4.runtime.CommonToken;
import org.antlr.v4.runtime.InputMismatchException;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.Pair;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import static com.nfl.glitr.util.NodeUtil.COMPLEXITY_FORMULA_KEY;
import static com.nfl.glitr.util.NodeUtil.COMPLEXITY_IGNORE_KEY;
import static org.apache.commons.lang3.StringUtils.defaultString;
     * @param variables graphQL query variables
     * @return {@link QueryComplexityNode}
     */
    public QueryComplexityNode queryScoreDetails(String query, Map<String, Object> variables) {
        if (StringUtils.isBlank(query)) {
            throw new GlitrException("query cannot be null or empty");
        }

        QueryComplexityNode complexityModel = buildComplexityModel(query);
        return queryScoreDetails(complexityModel, 0, new HashMap<>(), variables);
    }

    private QueryComplexityNode buildComplexityModel(String query) {
        Document document = parseRootNode(query);
        Map<String, Pair<String, List<Selection>>> fragments = getFragments(document);

        OperationDefinition operationDefinition = getFirstByType(document.getChildren(), OperationDefinition.class)
                .orElseThrow(() -> new GlitrException("Cannot find node 'OperationDefinition'"));

        Field queryEntryPoint = getFirstFieldNode(operationDefinition);
        if (queryEntryPoint == null) {
            return new QueryComplexityNode();
        }

        GraphQLFieldDefinition rootSchemaField = getRootSchemaField(query, queryEntryPoint.getName());

        boolean mutationQuery = operationDefinition.getOperation() == OperationDefinition.Operation.MUTATION;
        boolean ignoreField = ignoreFieldOrDefault(rootSchemaField, mutationQuery);

        QueryComplexityNode rootComplexityNode = new QueryComplexityNode(queryEntryPoint.getName());
// Path: src/main/java/com/nfl/glitr/exception/GlitrException.java
// public class GlitrException extends RuntimeException {
// 
//     public GlitrException(String message) {
//         super(message);
//     }
// 
//     public GlitrException(String message, Throwable cause) {
//         super(message, cause);
//     }
// }
// 
// Path: src/main/java/com/nfl/glitr/registry/schema/GlitrFieldDefinition.java
// public class GlitrFieldDefinition extends FieldDefinition {
// 
//     private Set<GlitrMetaDefinition> metaDefinitions = new HashSet<>();
// 
// 
//     public GlitrFieldDefinition(String name, Type type) {
//         super(name, type);
//     }
// 
//     public GlitrFieldDefinition(String name, Set<GlitrMetaDefinition> metaDefinitions) {
//         super(name, null);
//         this.metaDefinitions = metaDefinitions;
//     }
// 
//     public GlitrFieldDefinition(String name, Type type, Set<GlitrMetaDefinition> metaDefinitions) {
//         super(name, type);
//         this.metaDefinitions = metaDefinitions;
//     }
// 
//     public Set<GlitrMetaDefinition> getMetaDefinitions() {
//         return metaDefinitions;
//     }
// 
//     public void setMetaDefinitions(Set<GlitrMetaDefinition> metaDefinitions) {
//         this.metaDefinitions = metaDefinitions;
//     }
// 
//     public void addMetaDefinitions(GlitrMetaDefinition metaDefinition) {
//         if (this.metaDefinitions == null) {
//             this.metaDefinitions = new HashSet<>();
//         }
//         this.metaDefinitions.add(metaDefinition);
//     }
// }
// 
// Path: src/main/java/com/nfl/glitr/registry/schema/GlitrMetaDefinition.java
// public class GlitrMetaDefinition {
// 
//     private String name;
//     private Object value;
// 
// 
//     public GlitrMetaDefinition(String name, Object value) {
//         this.name = name;
//         this.value = value;
//     }
// 
//     public String getName() {
//         return name;
//     }
// 
//     public void setName(String name) {
//         this.name = name;
//     }
// 
//     public Object getValue() {
//         return value;
//     }
// 
//     public void setValue(Object value) {
//         this.value = value;
//     }
// 
//     @Override
//     public boolean equals(Object o) {
//         if (this == o) return true;
// 
//         if (o == null || getClass() != o.getClass()) return false;
// 
//         GlitrMetaDefinition that = (GlitrMetaDefinition) o;
// 
//         return new EqualsBuilder()
//                 .append(name, that.name)
//                 .isEquals();
//     }
// 
//     @Override
//     public int hashCode() {
//         return new HashCodeBuilder(17, 37)
//                 .append(name)
//                 .toHashCode();
//     }
// }
// 
// Path: src/main/java/com/nfl/glitr/registry/schema/GraphQLConnectionList.java
// public class GraphQLConnectionList extends GraphQLList {
//     public GraphQLConnectionList(GraphQLType wrappedType) {
//         super(wrappedType);
//     }
// }
// 
// Path: src/main/java/com/nfl/glitr/util/NodeUtil.java
// public static final String COMPLEXITY_FORMULA_KEY = "complexity_formula";
// 
// Path: src/main/java/com/nfl/glitr/util/NodeUtil.java
// public static final String COMPLEXITY_IGNORE_KEY = "complexity_ignore";


// Path: src/main/java/com/nfl/glitr/calculator/QueryComplexityCalculator.java
import com.nfl.glitr.exception.GlitrException;
import com.nfl.glitr.registry.schema.GlitrFieldDefinition;
import com.nfl.glitr.registry.schema.GlitrMetaDefinition;
import com.nfl.glitr.registry.schema.GraphQLConnectionList;
import graphql.language.*;
import graphql.parser.InvalidSyntaxException;
import graphql.parser.Parser;
import graphql.parser.antlr.GraphqlLexer;
import graphql.schema.*;
import net.objecthunter.exp4j.Expression;
import net.objecthunter.exp4j.ExpressionBuilder;
import org.antlr.v4.runtime.CharStreams;
import org.antlr.v4.runtime.CommonToken;
import org.antlr.v4.runtime.InputMismatchException;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.Pair;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import static com.nfl.glitr.util.NodeUtil.COMPLEXITY_FORMULA_KEY;
import static com.nfl.glitr.util.NodeUtil.COMPLEXITY_IGNORE_KEY;
import static org.apache.commons.lang3.StringUtils.defaultString;

     * @param variables graphQL query variables
     * @return {@link QueryComplexityNode}
     */
    public QueryComplexityNode queryScoreDetails(String query, Map<String, Object> variables) {
        if (StringUtils.isBlank(query)) {
            throw new GlitrException("query cannot be null or empty");
        }

        QueryComplexityNode complexityModel = buildComplexityModel(query);
        return queryScoreDetails(complexityModel, 0, new HashMap<>(), variables);
    }

    private QueryComplexityNode buildComplexityModel(String query) {
        Document document = parseRootNode(query);
        Map<String, Pair<String, List<Selection>>> fragments = getFragments(document);

        OperationDefinition operationDefinition = getFirstByType(document.getChildren(), OperationDefinition.class)
                .orElseThrow(() -> new GlitrException("Cannot find node 'OperationDefinition'"));

        Field queryEntryPoint = getFirstFieldNode(operationDefinition);
        if (queryEntryPoint == null) {
            return new QueryComplexityNode();
        }

        GraphQLFieldDefinition rootSchemaField = getRootSchemaField(query, queryEntryPoint.getName());

        boolean mutationQuery = operationDefinition.getOperation() == OperationDefinition.Operation.MUTATION;
        boolean ignoreField = ignoreFieldOrDefault(rootSchemaField, mutationQuery);

        QueryComplexityNode rootComplexityNode = new QueryComplexityNode(queryEntryPoint.getName());
        rootComplexityNode.setFormula(getGraphQLMeta(rootSchemaField, COMPLEXITY_FORMULA_KEY));