"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.GraphQLDocumentMode = exports.getTypeInfo = exports.canUseDirective = exports.runOnlineParser = exports.getTokenAtPosition = exports.getFragmentDefinitions = exports.getVariableCompletions = exports.getAutocompleteSuggestions = exports.SuggestionCommand = void 0;
const graphql_1 = require("graphql");
const types_1 = require("../types");
const parser_1 = require("../parser");
const autocompleteUtils_1 = require("./autocompleteUtils");
exports.SuggestionCommand = {
    command: 'editor.action.triggerSuggest',
    title: 'Suggestions',
};
const collectFragmentDefs = (op) => {
    const externalFragments = [];
    if (op) {
        try {
            (0, graphql_1.visit)((0, graphql_1.parse)(op), {
                FragmentDefinition(def) {
                    externalFragments.push(def);
                },
            });
        }
        catch (_a) {
            return [];
        }
    }
    return externalFragments;
};
const typeSystemKinds = [
    graphql_1.Kind.SCHEMA_DEFINITION,
    graphql_1.Kind.OPERATION_TYPE_DEFINITION,
    graphql_1.Kind.SCALAR_TYPE_DEFINITION,
    graphql_1.Kind.OBJECT_TYPE_DEFINITION,
    graphql_1.Kind.INTERFACE_TYPE_DEFINITION,
    graphql_1.Kind.UNION_TYPE_DEFINITION,
    graphql_1.Kind.ENUM_TYPE_DEFINITION,
    graphql_1.Kind.INPUT_OBJECT_TYPE_DEFINITION,
    graphql_1.Kind.DIRECTIVE_DEFINITION,
    graphql_1.Kind.SCHEMA_EXTENSION,
    graphql_1.Kind.SCALAR_TYPE_EXTENSION,
    graphql_1.Kind.OBJECT_TYPE_EXTENSION,
    graphql_1.Kind.INTERFACE_TYPE_EXTENSION,
    graphql_1.Kind.UNION_TYPE_EXTENSION,
    graphql_1.Kind.ENUM_TYPE_EXTENSION,
    graphql_1.Kind.INPUT_OBJECT_TYPE_EXTENSION,
];
const hasTypeSystemDefinitions = (sdl) => {
    let hasTypeSystemDef = false;
    if (sdl) {
        try {
            (0, graphql_1.visit)((0, graphql_1.parse)(sdl), {
                enter(node) {
                    if (node.kind === 'Document') {
                        return;
                    }
                    if (typeSystemKinds.includes(node.kind)) {
                        hasTypeSystemDef = true;
                        return graphql_1.BREAK;
                    }
                    return false;
                },
            });
        }
        catch (_a) {
            return hasTypeSystemDef;
        }
    }
    return hasTypeSystemDef;
};
function getAutocompleteSuggestions(schema, queryText, cursor, contextToken, fragmentDefs, options) {
    var _a;
    const opts = Object.assign(Object.assign({}, options), { schema });
    const token = contextToken || getTokenAtPosition(queryText, cursor, 1);
    const state = token.state.kind === 'Invalid' ? token.state.prevState : token.state;
    const mode = (options === null || options === void 0 ? void 0 : options.mode) || getDocumentMode(queryText, options === null || options === void 0 ? void 0 : options.uri);
    if (!state) {
        return [];
    }
    const { kind, step, prevState } = state;
    const typeInfo = getTypeInfo(schema, token.state);
    if (kind === parser_1.RuleKinds.DOCUMENT) {
        if (mode === GraphQLDocumentMode.TYPE_SYSTEM) {
            return getSuggestionsForTypeSystemDefinitions(token);
        }
        return getSuggestionsForExecutableDefinitions(token);
    }
    if (kind === parser_1.RuleKinds.EXTEND_DEF) {
        return getSuggestionsForExtensionDefinitions(token);
    }
    if (((_a = prevState === null || prevState === void 0 ? void 0 : prevState.prevState) === null || _a === void 0 ? void 0 : _a.kind) === parser_1.RuleKinds.EXTENSION_DEFINITION &&
        state.name) {
        return (0, autocompleteUtils_1.hintList)(token, []);
    }
    if ((prevState === null || prevState === void 0 ? void 0 : prevState.kind) === graphql_1.Kind.SCALAR_TYPE_EXTENSION) {
        return (0, autocompleteUtils_1.hintList)(token, Object.values(schema.getTypeMap())
            .filter(graphql_1.isScalarType)
            .map(type => ({
            label: type.name,
            kind: types_1.CompletionItemKind.Function,
        })));
    }
    if ((prevState === null || prevState === void 0 ? void 0 : prevState.kind) === graphql_1.Kind.OBJECT_TYPE_EXTENSION) {
        return (0, autocompleteUtils_1.hintList)(token, Object.values(schema.getTypeMap())
            .filter(type => (0, graphql_1.isObjectType)(type) && !type.name.startsWith('__'))
            .map(type => ({
            label: type.name,
            kind: types_1.CompletionItemKind.Function,
        })));
    }
    if ((prevState === null || prevState === void 0 ? void 0 : prevState.kind) === graphql_1.Kind.INTERFACE_TYPE_EXTENSION) {
        return (0, autocompleteUtils_1.hintList)(token, Object.values(schema.getTypeMap())
            .filter(graphql_1.isInterfaceType)
            .map(type => ({
            label: type.name,
            kind: types_1.CompletionItemKind.Function,
        })));
    }
    if ((prevState === null || prevState === void 0 ? void 0 : prevState.kind) === graphql_1.Kind.UNION_TYPE_EXTENSION) {
        return (0, autocompleteUtils_1.hintList)(token, Object.values(schema.getTypeMap())
            .filter(graphql_1.isUnionType)
            .map(type => ({
            label: type.name,
            kind: types_1.CompletionItemKind.Function,
        })));
    }
    if ((prevState === null || prevState === void 0 ? void 0 : prevState.kind) === graphql_1.Kind.ENUM_TYPE_EXTENSION) {
        return (0, autocompleteUtils_1.hintList)(token, Object.values(schema.getTypeMap())
            .filter(type => (0, graphql_1.isEnumType)(type) && !type.name.startsWith('__'))
            .map(type => ({
            label: type.name,
            kind: types_1.CompletionItemKind.Function,
        })));
    }
    if ((prevState === null || prevState === void 0 ? void 0 : prevState.kind) === graphql_1.Kind.INPUT_OBJECT_TYPE_EXTENSION) {
        return (0, autocompleteUtils_1.hintList)(token, Object.values(schema.getTypeMap())
            .filter(graphql_1.isInputObjectType)
            .map(type => ({
            label: type.name,
            kind: types_1.CompletionItemKind.Function,
        })));
    }
    if (kind === parser_1.RuleKinds.IMPLEMENTS ||
        (kind === parser_1.RuleKinds.NAMED_TYPE && (prevState === null || prevState === void 0 ? void 0 : prevState.kind) === parser_1.RuleKinds.IMPLEMENTS)) {
        return getSuggestionsForImplements(token, state, schema, queryText, typeInfo);
    }
    if (kind === parser_1.RuleKinds.SELECTION_SET ||
        kind === parser_1.RuleKinds.FIELD ||
        kind === parser_1.RuleKinds.ALIASED_FIELD) {
        return getSuggestionsForFieldNames(token, typeInfo, opts);
    }
    if (kind === parser_1.RuleKinds.ARGUMENTS ||
        (kind === parser_1.RuleKinds.ARGUMENT && step === 0)) {
        const { argDefs } = typeInfo;
        if (argDefs) {
            return (0, autocompleteUtils_1.hintList)(token, argDefs.map((argDef) => {
                var _a;
                return ({
                    label: argDef.name,
                    insertText: argDef.name + ': ',
                    command: exports.SuggestionCommand,
                    detail: String(argDef.type),
                    documentation: (_a = argDef.description) !== null && _a !== void 0 ? _a : undefined,
                    kind: types_1.CompletionItemKind.Variable,
                    type: argDef.type,
                });
            }));
        }
    }
    if ((kind === parser_1.RuleKinds.OBJECT_VALUE ||
        (kind === parser_1.RuleKinds.OBJECT_FIELD && step === 0)) &&
        typeInfo.objectFieldDefs) {
        const objectFields = (0, autocompleteUtils_1.objectValues)(typeInfo.objectFieldDefs);
        const completionKind = kind === parser_1.RuleKinds.OBJECT_VALUE
            ? types_1.CompletionItemKind.Value
            : types_1.CompletionItemKind.Field;
        return (0, autocompleteUtils_1.hintList)(token, objectFields.map(field => {
            var _a;
            return ({
                label: field.name,
                detail: String(field.type),
                documentation: (_a = field.description) !== null && _a !== void 0 ? _a : undefined,
                kind: completionKind,
                type: field.type,
            });
        }));
    }
    if (kind === parser_1.RuleKinds.ENUM_VALUE ||
        (kind === parser_1.RuleKinds.LIST_VALUE && step === 1) ||
        (kind === parser_1.RuleKinds.OBJECT_FIELD && step === 2) ||
        (kind === parser_1.RuleKinds.ARGUMENT && step === 2)) {
        return getSuggestionsForInputValues(token, typeInfo, queryText, schema);
    }
    if (kind === parser_1.RuleKinds.VARIABLE && step === 1) {
        const namedInputType = (0, graphql_1.getNamedType)(typeInfo.inputType);
        const variableDefinitions = getVariableCompletions(queryText, schema, token);
        return (0, autocompleteUtils_1.hintList)(token, variableDefinitions.filter(v => v.detail === (namedInputType === null || namedInputType === void 0 ? void 0 : namedInputType.name)));
    }
    if ((kind === parser_1.RuleKinds.TYPE_CONDITION && step === 1) ||
        (kind === parser_1.RuleKinds.NAMED_TYPE &&
            prevState != null &&
            prevState.kind === parser_1.RuleKinds.TYPE_CONDITION)) {
        return getSuggestionsForFragmentTypeConditions(token, typeInfo, schema, kind);
    }
    if (kind === parser_1.RuleKinds.FRAGMENT_SPREAD && step === 1) {
        return getSuggestionsForFragmentSpread(token, typeInfo, schema, queryText, Array.isArray(fragmentDefs)
            ? fragmentDefs
            : collectFragmentDefs(fragmentDefs));
    }
    const unwrappedState = unwrapType(state);
    if ((mode === GraphQLDocumentMode.TYPE_SYSTEM &&
        !unwrappedState.needsAdvance &&
        kind === parser_1.RuleKinds.NAMED_TYPE) ||
        kind === parser_1.RuleKinds.LIST_TYPE) {
        if (unwrappedState.kind === parser_1.RuleKinds.FIELD_DEF) {
            return (0, autocompleteUtils_1.hintList)(token, Object.values(schema.getTypeMap())
                .filter(type => (0, graphql_1.isOutputType)(type) && !type.name.startsWith('__'))
                .map(type => ({
                label: type.name,
                kind: types_1.CompletionItemKind.Function,
            })));
        }
        if (unwrappedState.kind === parser_1.RuleKinds.INPUT_VALUE_DEF) {
            return (0, autocompleteUtils_1.hintList)(token, Object.values(schema.getTypeMap())
                .filter(type => (0, graphql_1.isInputType)(type) && !type.name.startsWith('__'))
                .map(type => ({
                label: type.name,
                kind: types_1.CompletionItemKind.Function,
            })));
        }
    }
    if ((kind === parser_1.RuleKinds.VARIABLE_DEFINITION && step === 2) ||
        (kind === parser_1.RuleKinds.LIST_TYPE && step === 1) ||
        (kind === parser_1.RuleKinds.NAMED_TYPE &&
            prevState &&
            (prevState.kind === parser_1.RuleKinds.VARIABLE_DEFINITION ||
                prevState.kind === parser_1.RuleKinds.LIST_TYPE ||
                prevState.kind === parser_1.RuleKinds.NON_NULL_TYPE))) {
        return getSuggestionsForVariableDefinition(token, schema, kind);
    }
    if (kind === parser_1.RuleKinds.DIRECTIVE) {
        return getSuggestionsForDirective(token, state, schema, kind);
    }
    return [];
}
exports.getAutocompleteSuggestions = getAutocompleteSuggestions;
const insertSuffix = ' {\n  $1\n}';
const getInsertText = (field) => {
    const { type } = field;
    if ((0, graphql_1.isCompositeType)(type)) {
        return insertSuffix;
    }
    if ((0, graphql_1.isListType)(type) && (0, graphql_1.isCompositeType)(type.ofType)) {
        return insertSuffix;
    }
    if ((0, graphql_1.isNonNullType)(type)) {
        if ((0, graphql_1.isCompositeType)(type.ofType)) {
            return insertSuffix;
        }
        if ((0, graphql_1.isListType)(type.ofType) && (0, graphql_1.isCompositeType)(type.ofType.ofType)) {
            return insertSuffix;
        }
    }
    return null;
};
function getSuggestionsForTypeSystemDefinitions(token) {
    return (0, autocompleteUtils_1.hintList)(token, [
        { label: 'extend', kind: types_1.CompletionItemKind.Function },
        { label: 'type', kind: types_1.CompletionItemKind.Function },
        { label: 'interface', kind: types_1.CompletionItemKind.Function },
        { label: 'union', kind: types_1.CompletionItemKind.Function },
        { label: 'input', kind: types_1.CompletionItemKind.Function },
        { label: 'scalar', kind: types_1.CompletionItemKind.Function },
        { label: 'schema', kind: types_1.CompletionItemKind.Function },
    ]);
}
function getSuggestionsForExecutableDefinitions(token) {
    return (0, autocompleteUtils_1.hintList)(token, [
        { label: 'query', kind: types_1.CompletionItemKind.Function },
        { label: 'mutation', kind: types_1.CompletionItemKind.Function },
        { label: 'subscription', kind: types_1.CompletionItemKind.Function },
        { label: 'fragment', kind: types_1.CompletionItemKind.Function },
        { label: '{', kind: types_1.CompletionItemKind.Constructor },
    ]);
}
function getSuggestionsForExtensionDefinitions(token) {
    return (0, autocompleteUtils_1.hintList)(token, [
        { label: 'type', kind: types_1.CompletionItemKind.Function },
        { label: 'interface', kind: types_1.CompletionItemKind.Function },
        { label: 'union', kind: types_1.CompletionItemKind.Function },
        { label: 'input', kind: types_1.CompletionItemKind.Function },
        { label: 'scalar', kind: types_1.CompletionItemKind.Function },
        { label: 'schema', kind: types_1.CompletionItemKind.Function },
    ]);
}
function getSuggestionsForFieldNames(token, typeInfo, options) {
    var _a;
    if (typeInfo.parentType) {
        const { parentType } = typeInfo;
        let fields = [];
        if ('getFields' in parentType) {
            fields = (0, autocompleteUtils_1.objectValues)(parentType.getFields());
        }
        if ((0, graphql_1.isCompositeType)(parentType)) {
            fields.push(graphql_1.TypeNameMetaFieldDef);
        }
        if (parentType === ((_a = options === null || options === void 0 ? void 0 : options.schema) === null || _a === void 0 ? void 0 : _a.getQueryType())) {
            fields.push(graphql_1.SchemaMetaFieldDef, graphql_1.TypeMetaFieldDef);
        }
        return (0, autocompleteUtils_1.hintList)(token, fields.map((field, index) => {
            var _a;
            const suggestion = {
                sortText: String(index) + field.name,
                label: field.name,
                detail: String(field.type),
                documentation: (_a = field.description) !== null && _a !== void 0 ? _a : undefined,
                deprecated: Boolean(field.deprecationReason),
                isDeprecated: Boolean(field.deprecationReason),
                deprecationReason: field.deprecationReason,
                kind: types_1.CompletionItemKind.Field,
                type: field.type,
            };
            if (options === null || options === void 0 ? void 0 : options.fillLeafsOnComplete) {
                const insertText = getInsertText(field);
                if (insertText) {
                    suggestion.insertText = field.name + insertText;
                    suggestion.insertTextFormat = types_1.InsertTextFormat.Snippet;
                    suggestion.command = exports.SuggestionCommand;
                }
            }
            return suggestion;
        }));
    }
    return [];
}
function getSuggestionsForInputValues(token, typeInfo, queryText, schema) {
    const namedInputType = (0, graphql_1.getNamedType)(typeInfo.inputType);
    const queryVariables = getVariableCompletions(queryText, schema, token).filter(v => v.detail === namedInputType.name);
    if (namedInputType instanceof graphql_1.GraphQLEnumType) {
        const values = namedInputType.getValues();
        return (0, autocompleteUtils_1.hintList)(token, values
            .map((value) => {
            var _a;
            return ({
                label: value.name,
                detail: String(namedInputType),
                documentation: (_a = value.description) !== null && _a !== void 0 ? _a : undefined,
                deprecated: Boolean(value.deprecationReason),
                isDeprecated: Boolean(value.deprecationReason),
                deprecationReason: value.deprecationReason,
                kind: types_1.CompletionItemKind.EnumMember,
                type: namedInputType,
            });
        })
            .concat(queryVariables));
    }
    if (namedInputType === graphql_1.GraphQLBoolean) {
        return (0, autocompleteUtils_1.hintList)(token, queryVariables.concat([
            {
                label: 'true',
                detail: String(graphql_1.GraphQLBoolean),
                documentation: 'Not false.',
                kind: types_1.CompletionItemKind.Variable,
                type: graphql_1.GraphQLBoolean,
            },
            {
                label: 'false',
                detail: String(graphql_1.GraphQLBoolean),
                documentation: 'Not true.',
                kind: types_1.CompletionItemKind.Variable,
                type: graphql_1.GraphQLBoolean,
            },
        ]));
    }
    return queryVariables;
}
function getSuggestionsForImplements(token, tokenState, schema, documentText, typeInfo) {
    if (tokenState.needsSeparator) {
        return [];
    }
    const typeMap = schema.getTypeMap();
    const schemaInterfaces = (0, autocompleteUtils_1.objectValues)(typeMap).filter(graphql_1.isInterfaceType);
    const schemaInterfaceNames = schemaInterfaces.map(({ name }) => name);
    const inlineInterfaces = new Set();
    runOnlineParser(documentText, (_, state) => {
        var _a, _b, _c, _d, _e;
        if (state.name) {
            if (state.kind === parser_1.RuleKinds.INTERFACE_DEF &&
                !schemaInterfaceNames.includes(state.name)) {
                inlineInterfaces.add(state.name);
            }
            if (state.kind === parser_1.RuleKinds.NAMED_TYPE &&
                ((_a = state.prevState) === null || _a === void 0 ? void 0 : _a.kind) === parser_1.RuleKinds.IMPLEMENTS) {
                if (typeInfo.interfaceDef) {
                    const existingType = (_b = typeInfo.interfaceDef) === null || _b === void 0 ? void 0 : _b.getInterfaces().find(({ name }) => name === state.name);
                    if (existingType) {
                        return;
                    }
                    const type = schema.getType(state.name);
                    const interfaceConfig = (_c = typeInfo.interfaceDef) === null || _c === void 0 ? void 0 : _c.toConfig();
                    typeInfo.interfaceDef = new graphql_1.GraphQLInterfaceType(Object.assign(Object.assign({}, interfaceConfig), { interfaces: [
                            ...interfaceConfig.interfaces,
                            type ||
                                new graphql_1.GraphQLInterfaceType({ name: state.name, fields: {} }),
                        ] }));
                }
                else if (typeInfo.objectTypeDef) {
                    const existingType = (_d = typeInfo.objectTypeDef) === null || _d === void 0 ? void 0 : _d.getInterfaces().find(({ name }) => name === state.name);
                    if (existingType) {
                        return;
                    }
                    const type = schema.getType(state.name);
                    const objectTypeConfig = (_e = typeInfo.objectTypeDef) === null || _e === void 0 ? void 0 : _e.toConfig();
                    typeInfo.objectTypeDef = new graphql_1.GraphQLObjectType(Object.assign(Object.assign({}, objectTypeConfig), { interfaces: [
                            ...objectTypeConfig.interfaces,
                            type ||
                                new graphql_1.GraphQLInterfaceType({ name: state.name, fields: {} }),
                        ] }));
                }
            }
        }
    });
    const currentTypeToExtend = typeInfo.interfaceDef || typeInfo.objectTypeDef;
    const siblingInterfaces = (currentTypeToExtend === null || currentTypeToExtend === void 0 ? void 0 : currentTypeToExtend.getInterfaces()) || [];
    const siblingInterfaceNames = siblingInterfaces.map(({ name }) => name);
    const possibleInterfaces = schemaInterfaces
        .concat([...inlineInterfaces].map(name => ({ name })))
        .filter(({ name }) => name !== (currentTypeToExtend === null || currentTypeToExtend === void 0 ? void 0 : currentTypeToExtend.name) &&
        !siblingInterfaceNames.includes(name));
    return (0, autocompleteUtils_1.hintList)(token, possibleInterfaces.map(type => {
        const result = {
            label: type.name,
            kind: types_1.CompletionItemKind.Interface,
            type,
        };
        if (type === null || type === void 0 ? void 0 : type.description) {
            result.documentation = type.description;
        }
        return result;
    }));
}
function getSuggestionsForFragmentTypeConditions(token, typeInfo, schema, _kind) {
    let possibleTypes;
    if (typeInfo.parentType) {
        if ((0, graphql_1.isAbstractType)(typeInfo.parentType)) {
            const abstractType = (0, graphql_1.assertAbstractType)(typeInfo.parentType);
            const possibleObjTypes = schema.getPossibleTypes(abstractType);
            const possibleIfaceMap = Object.create(null);
            for (const type of possibleObjTypes) {
                for (const iface of type.getInterfaces()) {
                    possibleIfaceMap[iface.name] = iface;
                }
            }
            possibleTypes = possibleObjTypes.concat((0, autocompleteUtils_1.objectValues)(possibleIfaceMap));
        }
        else {
            possibleTypes = [typeInfo.parentType];
        }
    }
    else {
        const typeMap = schema.getTypeMap();
        possibleTypes = (0, autocompleteUtils_1.objectValues)(typeMap).filter(type => (0, graphql_1.isCompositeType)(type) && !type.name.startsWith('__'));
    }
    return (0, autocompleteUtils_1.hintList)(token, possibleTypes.map(type => {
        const namedType = (0, graphql_1.getNamedType)(type);
        return {
            label: String(type),
            documentation: (namedType === null || namedType === void 0 ? void 0 : namedType.description) || '',
            kind: types_1.CompletionItemKind.Field,
        };
    }));
}
function getSuggestionsForFragmentSpread(token, typeInfo, schema, queryText, fragmentDefs) {
    if (!queryText) {
        return [];
    }
    const typeMap = schema.getTypeMap();
    const defState = (0, autocompleteUtils_1.getDefinitionState)(token.state);
    const fragments = getFragmentDefinitions(queryText);
    if (fragmentDefs && fragmentDefs.length > 0) {
        fragments.push(...fragmentDefs);
    }
    const relevantFrags = fragments.filter(frag => typeMap[frag.typeCondition.name.value] &&
        !(defState &&
            defState.kind === parser_1.RuleKinds.FRAGMENT_DEFINITION &&
            defState.name === frag.name.value) &&
        (0, graphql_1.isCompositeType)(typeInfo.parentType) &&
        (0, graphql_1.isCompositeType)(typeMap[frag.typeCondition.name.value]) &&
        (0, graphql_1.doTypesOverlap)(schema, typeInfo.parentType, typeMap[frag.typeCondition.name.value]));
    return (0, autocompleteUtils_1.hintList)(token, relevantFrags.map(frag => ({
        label: frag.name.value,
        detail: String(typeMap[frag.typeCondition.name.value]),
        documentation: `fragment ${frag.name.value} on ${frag.typeCondition.name.value}`,
        kind: types_1.CompletionItemKind.Field,
        type: typeMap[frag.typeCondition.name.value],
    })));
}
const getParentDefinition = (state, kind) => {
    var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k;
    if (((_a = state.prevState) === null || _a === void 0 ? void 0 : _a.kind) === kind) {
        return state.prevState;
    }
    if (((_c = (_b = state.prevState) === null || _b === void 0 ? void 0 : _b.prevState) === null || _c === void 0 ? void 0 : _c.kind) === kind) {
        return state.prevState.prevState;
    }
    if (((_f = (_e = (_d = state.prevState) === null || _d === void 0 ? void 0 : _d.prevState) === null || _e === void 0 ? void 0 : _e.prevState) === null || _f === void 0 ? void 0 : _f.kind) === kind) {
        return state.prevState.prevState.prevState;
    }
    if (((_k = (_j = (_h = (_g = state.prevState) === null || _g === void 0 ? void 0 : _g.prevState) === null || _h === void 0 ? void 0 : _h.prevState) === null || _j === void 0 ? void 0 : _j.prevState) === null || _k === void 0 ? void 0 : _k.kind) === kind) {
        return state.prevState.prevState.prevState.prevState;
    }
};
function getVariableCompletions(queryText, schema, token) {
    let variableName = null;
    let variableType;
    const definitions = Object.create({});
    runOnlineParser(queryText, (_, state) => {
        if ((state === null || state === void 0 ? void 0 : state.kind) === parser_1.RuleKinds.VARIABLE && state.name) {
            variableName = state.name;
        }
        if ((state === null || state === void 0 ? void 0 : state.kind) === parser_1.RuleKinds.NAMED_TYPE && variableName) {
            const parentDefinition = getParentDefinition(state, parser_1.RuleKinds.TYPE);
            if (parentDefinition === null || parentDefinition === void 0 ? void 0 : parentDefinition.type) {
                variableType = schema.getType(parentDefinition === null || parentDefinition === void 0 ? void 0 : parentDefinition.type);
            }
        }
        if (variableName && variableType && !definitions[variableName]) {
            definitions[variableName] = {
                detail: variableType.toString(),
                insertText: token.string === '$' ? variableName : '$' + variableName,
                label: variableName,
                type: variableType,
                kind: types_1.CompletionItemKind.Variable,
            };
            variableName = null;
            variableType = null;
        }
    });
    return (0, autocompleteUtils_1.objectValues)(definitions);
}
exports.getVariableCompletions = getVariableCompletions;
function getFragmentDefinitions(queryText) {
    const fragmentDefs = [];
    runOnlineParser(queryText, (_, state) => {
        if (state.kind === parser_1.RuleKinds.FRAGMENT_DEFINITION &&
            state.name &&
            state.type) {
            fragmentDefs.push({
                kind: parser_1.RuleKinds.FRAGMENT_DEFINITION,
                name: {
                    kind: graphql_1.Kind.NAME,
                    value: state.name,
                },
                selectionSet: {
                    kind: parser_1.RuleKinds.SELECTION_SET,
                    selections: [],
                },
                typeCondition: {
                    kind: parser_1.RuleKinds.NAMED_TYPE,
                    name: {
                        kind: graphql_1.Kind.NAME,
                        value: state.type,
                    },
                },
            });
        }
    });
    return fragmentDefs;
}
exports.getFragmentDefinitions = getFragmentDefinitions;
function getSuggestionsForVariableDefinition(token, schema, _kind) {
    const inputTypeMap = schema.getTypeMap();
    const inputTypes = (0, autocompleteUtils_1.objectValues)(inputTypeMap).filter(graphql_1.isInputType);
    return (0, autocompleteUtils_1.hintList)(token, inputTypes.map((type) => ({
        label: type.name,
        documentation: type.description,
        kind: types_1.CompletionItemKind.Variable,
    })));
}
function getSuggestionsForDirective(token, state, schema, _kind) {
    var _a;
    if ((_a = state.prevState) === null || _a === void 0 ? void 0 : _a.kind) {
        const directives = schema
            .getDirectives()
            .filter(directive => canUseDirective(state.prevState, directive));
        return (0, autocompleteUtils_1.hintList)(token, directives.map(directive => ({
            label: directive.name,
            documentation: directive.description || '',
            kind: types_1.CompletionItemKind.Function,
        })));
    }
    return [];
}
function getTokenAtPosition(queryText, cursor, offset = 0) {
    let styleAtCursor = null;
    let stateAtCursor = null;
    let stringAtCursor = null;
    const token = runOnlineParser(queryText, (stream, state, style, index) => {
        if (index !== cursor.line ||
            stream.getCurrentPosition() + offset < cursor.character + 1) {
            return;
        }
        styleAtCursor = style;
        stateAtCursor = Object.assign({}, state);
        stringAtCursor = stream.current();
        return 'BREAK';
    });
    return {
        start: token.start,
        end: token.end,
        string: stringAtCursor || token.string,
        state: stateAtCursor || token.state,
        style: styleAtCursor || token.style,
    };
}
exports.getTokenAtPosition = getTokenAtPosition;
function runOnlineParser(queryText, callback) {
    const lines = queryText.split('\n');
    const parser = (0, parser_1.onlineParser)();
    let state = parser.startState();
    let style = '';
    let stream = new parser_1.CharacterStream('');
    for (let i = 0; i < lines.length; i++) {
        stream = new parser_1.CharacterStream(lines[i]);
        while (!stream.eol()) {
            style = parser.token(stream, state);
            const code = callback(stream, state, style, i);
            if (code === 'BREAK') {
                break;
            }
        }
        callback(stream, state, style, i);
        if (!state.kind) {
            state = parser.startState();
        }
    }
    return {
        start: stream.getStartOfToken(),
        end: stream.getCurrentPosition(),
        string: stream.current(),
        state,
        style,
    };
}
exports.runOnlineParser = runOnlineParser;
function canUseDirective(state, directive) {
    if (!(state === null || state === void 0 ? void 0 : state.kind)) {
        return false;
    }
    const { kind, prevState } = state;
    const { locations } = directive;
    switch (kind) {
        case parser_1.RuleKinds.QUERY:
            return locations.includes(graphql_1.DirectiveLocation.QUERY);
        case parser_1.RuleKinds.MUTATION:
            return locations.includes(graphql_1.DirectiveLocation.MUTATION);
        case parser_1.RuleKinds.SUBSCRIPTION:
            return locations.includes(graphql_1.DirectiveLocation.SUBSCRIPTION);
        case parser_1.RuleKinds.FIELD:
        case parser_1.RuleKinds.ALIASED_FIELD:
            return locations.includes(graphql_1.DirectiveLocation.FIELD);
        case parser_1.RuleKinds.FRAGMENT_DEFINITION:
            return locations.includes(graphql_1.DirectiveLocation.FRAGMENT_DEFINITION);
        case parser_1.RuleKinds.FRAGMENT_SPREAD:
            return locations.includes(graphql_1.DirectiveLocation.FRAGMENT_SPREAD);
        case parser_1.RuleKinds.INLINE_FRAGMENT:
            return locations.includes(graphql_1.DirectiveLocation.INLINE_FRAGMENT);
        case parser_1.RuleKinds.SCHEMA_DEF:
            return locations.includes(graphql_1.DirectiveLocation.SCHEMA);
        case parser_1.RuleKinds.SCALAR_DEF:
            return locations.includes(graphql_1.DirectiveLocation.SCALAR);
        case parser_1.RuleKinds.OBJECT_TYPE_DEF:
            return locations.includes(graphql_1.DirectiveLocation.OBJECT);
        case parser_1.RuleKinds.FIELD_DEF:
            return locations.includes(graphql_1.DirectiveLocation.FIELD_DEFINITION);
        case parser_1.RuleKinds.INTERFACE_DEF:
            return locations.includes(graphql_1.DirectiveLocation.INTERFACE);
        case parser_1.RuleKinds.UNION_DEF:
            return locations.includes(graphql_1.DirectiveLocation.UNION);
        case parser_1.RuleKinds.ENUM_DEF:
            return locations.includes(graphql_1.DirectiveLocation.ENUM);
        case parser_1.RuleKinds.ENUM_VALUE:
            return locations.includes(graphql_1.DirectiveLocation.ENUM_VALUE);
        case parser_1.RuleKinds.INPUT_DEF:
            return locations.includes(graphql_1.DirectiveLocation.INPUT_OBJECT);
        case parser_1.RuleKinds.INPUT_VALUE_DEF:
            const prevStateKind = prevState === null || prevState === void 0 ? void 0 : prevState.kind;
            switch (prevStateKind) {
                case parser_1.RuleKinds.ARGUMENTS_DEF:
                    return locations.includes(graphql_1.DirectiveLocation.ARGUMENT_DEFINITION);
                case parser_1.RuleKinds.INPUT_DEF:
                    return locations.includes(graphql_1.DirectiveLocation.INPUT_FIELD_DEFINITION);
            }
    }
    return false;
}
exports.canUseDirective = canUseDirective;
function getTypeInfo(schema, tokenState) {
    let argDef;
    let argDefs;
    let directiveDef;
    let enumValue;
    let fieldDef;
    let inputType;
    let objectTypeDef;
    let objectFieldDefs;
    let parentType;
    let type;
    let interfaceDef;
    (0, autocompleteUtils_1.forEachState)(tokenState, state => {
        var _a;
        switch (state.kind) {
            case parser_1.RuleKinds.QUERY:
            case 'ShortQuery':
                type = schema.getQueryType();
                break;
            case parser_1.RuleKinds.MUTATION:
                type = schema.getMutationType();
                break;
            case parser_1.RuleKinds.SUBSCRIPTION:
                type = schema.getSubscriptionType();
                break;
            case parser_1.RuleKinds.INLINE_FRAGMENT:
            case parser_1.RuleKinds.FRAGMENT_DEFINITION:
                if (state.type) {
                    type = schema.getType(state.type);
                }
                break;
            case parser_1.RuleKinds.FIELD:
            case parser_1.RuleKinds.ALIASED_FIELD: {
                if (!type || !state.name) {
                    fieldDef = null;
                }
                else {
                    fieldDef = parentType
                        ? (0, autocompleteUtils_1.getFieldDef)(schema, parentType, state.name)
                        : null;
                    type = fieldDef ? fieldDef.type : null;
                }
                break;
            }
            case parser_1.RuleKinds.SELECTION_SET:
                parentType = (0, graphql_1.getNamedType)(type);
                break;
            case parser_1.RuleKinds.DIRECTIVE:
                directiveDef = state.name ? schema.getDirective(state.name) : null;
                break;
            case parser_1.RuleKinds.INTERFACE_DEF:
                if (state.name) {
                    objectTypeDef = null;
                    interfaceDef = new graphql_1.GraphQLInterfaceType({
                        name: state.name,
                        interfaces: [],
                        fields: {},
                    });
                }
                break;
            case parser_1.RuleKinds.OBJECT_TYPE_DEF:
                if (state.name) {
                    interfaceDef = null;
                    objectTypeDef = new graphql_1.GraphQLObjectType({
                        name: state.name,
                        interfaces: [],
                        fields: {},
                    });
                }
                break;
            case parser_1.RuleKinds.ARGUMENTS: {
                if (state.prevState) {
                    switch (state.prevState.kind) {
                        case parser_1.RuleKinds.FIELD:
                            argDefs = fieldDef && fieldDef.args;
                            break;
                        case parser_1.RuleKinds.DIRECTIVE:
                            argDefs =
                                directiveDef && directiveDef.args;
                            break;
                        case parser_1.RuleKinds.ALIASED_FIELD: {
                            const name = (_a = state.prevState) === null || _a === void 0 ? void 0 : _a.name;
                            if (!name) {
                                argDefs = null;
                                break;
                            }
                            const field = parentType
                                ? (0, autocompleteUtils_1.getFieldDef)(schema, parentType, name)
                                : null;
                            if (!field) {
                                argDefs = null;
                                break;
                            }
                            argDefs = field.args;
                            break;
                        }
                        default:
                            argDefs = null;
                            break;
                    }
                }
                else {
                    argDefs = null;
                }
                break;
            }
            case parser_1.RuleKinds.ARGUMENT:
                if (argDefs) {
                    for (let i = 0; i < argDefs.length; i++) {
                        if (argDefs[i].name === state.name) {
                            argDef = argDefs[i];
                            break;
                        }
                    }
                }
                inputType = argDef === null || argDef === void 0 ? void 0 : argDef.type;
                break;
            case parser_1.RuleKinds.ENUM_VALUE:
                const enumType = (0, graphql_1.getNamedType)(inputType);
                enumValue =
                    enumType instanceof graphql_1.GraphQLEnumType
                        ? enumType
                            .getValues()
                            .find((val) => val.value === state.name)
                        : null;
                break;
            case parser_1.RuleKinds.LIST_VALUE:
                const nullableType = (0, graphql_1.getNullableType)(inputType);
                inputType =
                    nullableType instanceof graphql_1.GraphQLList ? nullableType.ofType : null;
                break;
            case parser_1.RuleKinds.OBJECT_VALUE:
                const objectType = (0, graphql_1.getNamedType)(inputType);
                objectFieldDefs =
                    objectType instanceof graphql_1.GraphQLInputObjectType
                        ? objectType.getFields()
                        : null;
                break;
            case parser_1.RuleKinds.OBJECT_FIELD:
                const objectField = state.name && objectFieldDefs ? objectFieldDefs[state.name] : null;
                inputType = objectField === null || objectField === void 0 ? void 0 : objectField.type;
                break;
            case parser_1.RuleKinds.NAMED_TYPE:
                if (state.name) {
                    type = schema.getType(state.name);
                }
                break;
        }
    });
    return {
        argDef,
        argDefs,
        directiveDef,
        enumValue,
        fieldDef,
        inputType,
        objectFieldDefs,
        parentType,
        type,
        interfaceDef,
        objectTypeDef,
    };
}
exports.getTypeInfo = getTypeInfo;
var GraphQLDocumentMode;
(function (GraphQLDocumentMode) {
    GraphQLDocumentMode["TYPE_SYSTEM"] = "TYPE_SYSTEM";
    GraphQLDocumentMode["EXECUTABLE"] = "EXECUTABLE";
})(GraphQLDocumentMode = exports.GraphQLDocumentMode || (exports.GraphQLDocumentMode = {}));
function getDocumentMode(documentText, uri) {
    if (uri === null || uri === void 0 ? void 0 : uri.endsWith('.graphqls')) {
        return GraphQLDocumentMode.TYPE_SYSTEM;
    }
    return hasTypeSystemDefinitions(documentText)
        ? GraphQLDocumentMode.TYPE_SYSTEM
        : GraphQLDocumentMode.EXECUTABLE;
}
function unwrapType(state) {
    if (state.prevState &&
        state.kind &&
        [
            parser_1.RuleKinds.NAMED_TYPE,
            parser_1.RuleKinds.LIST_TYPE,
            parser_1.RuleKinds.TYPE,
            parser_1.RuleKinds.NON_NULL_TYPE,
        ].includes(state.kind)) {
        return unwrapType(state.prevState);
    }
    return state;
}
//# sourceMappingURL=getAutocompleteSuggestions.js.map