import { TypeInfo, getNamedType, visit, visitWithTypeInfo, Kind, } from 'graphql';
function uniqueBy(array, iteratee) {
    var _a;
    const FilteredMap = new Map();
    const result = [];
    for (const item of array) {
        if (item.kind === 'Field') {
            const uniqueValue = iteratee(item);
            const existing = FilteredMap.get(uniqueValue);
            if ((_a = item.directives) === null || _a === void 0 ? void 0 : _a.length) {
                const itemClone = Object.assign({}, item);
                result.push(itemClone);
            }
            else if ((existing === null || existing === void 0 ? void 0 : existing.selectionSet) && item.selectionSet) {
                existing.selectionSet.selections = [
                    ...existing.selectionSet.selections,
                    ...item.selectionSet.selections,
                ];
            }
            else if (!existing) {
                const itemClone = Object.assign({}, item);
                FilteredMap.set(uniqueValue, itemClone);
                result.push(itemClone);
            }
        }
        else {
            result.push(item);
        }
    }
    return result;
}
function inlineRelevantFragmentSpreads(fragmentDefinitions, selections, selectionSetType) {
    var _a;
    const selectionSetTypeName = selectionSetType
        ? getNamedType(selectionSetType).name
        : null;
    const outputSelections = [];
    const seenSpreads = [];
    for (let selection of selections) {
        if (selection.kind === 'FragmentSpread') {
            const fragmentName = selection.name.value;
            if (!selection.directives || selection.directives.length === 0) {
                if (seenSpreads.includes(fragmentName)) {
                    continue;
                }
                else {
                    seenSpreads.push(fragmentName);
                }
            }
            const fragmentDefinition = fragmentDefinitions[selection.name.value];
            if (fragmentDefinition) {
                const { typeCondition, directives, selectionSet } = fragmentDefinition;
                selection = {
                    kind: Kind.INLINE_FRAGMENT,
                    typeCondition,
                    directives,
                    selectionSet,
                };
            }
        }
        if (selection.kind === Kind.INLINE_FRAGMENT &&
            (!selection.directives || ((_a = selection.directives) === null || _a === void 0 ? void 0 : _a.length) === 0)) {
            const fragmentTypeName = selection.typeCondition
                ? selection.typeCondition.name.value
                : null;
            if (!fragmentTypeName || fragmentTypeName === selectionSetTypeName) {
                outputSelections.push(...inlineRelevantFragmentSpreads(fragmentDefinitions, selection.selectionSet.selections, selectionSetType));
                continue;
            }
        }
        outputSelections.push(selection);
    }
    return outputSelections;
}
export function mergeAst(documentAST, schema) {
    const typeInfo = schema ? new TypeInfo(schema) : null;
    const fragmentDefinitions = Object.create(null);
    for (const definition of documentAST.definitions) {
        if (definition.kind === Kind.FRAGMENT_DEFINITION) {
            fragmentDefinitions[definition.name.value] = definition;
        }
    }
    const flattenVisitors = {
        SelectionSet(node) {
            const selectionSetType = typeInfo ? typeInfo.getParentType() : null;
            let { selections } = node;
            selections = inlineRelevantFragmentSpreads(fragmentDefinitions, selections, selectionSetType);
            return Object.assign(Object.assign({}, node), { selections });
        },
        FragmentDefinition() {
            return null;
        },
    };
    const flattenedAST = visit(documentAST, typeInfo ? visitWithTypeInfo(typeInfo, flattenVisitors) : flattenVisitors);
    const deduplicateVisitors = {
        SelectionSet(node) {
            let { selections } = node;
            selections = uniqueBy(selections, selection => selection.alias ? selection.alias.value : selection.name.value);
            return Object.assign(Object.assign({}, node), { selections });
        },
        FragmentDefinition() {
            return null;
        },
    };
    return visit(flattenedAST, deduplicateVisitors);
}
//# sourceMappingURL=merge-ast.js.map