"use strict";
var __rest = (this && this.__rest) || function (s, e) {
    var t = {};
    for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
        t[p] = s[p];
    if (s != null && typeof Object.getOwnPropertySymbols === "function")
        for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
            if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
                t[p[i]] = s[p[i]];
        }
    return t;
};
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.printBlockString = exports.printWithReducedWhitespace = exports.printType = exports.printIntrospectionSchema = exports.printComposedSdl = void 0;
const graphql_1 = require("graphql");
const types_1 = require("../types");
const utils_1 = require("../composition/utils");
const csdlDirectives_1 = __importDefault(require("../csdlDirectives"));
function printComposedSdl(schema, serviceList, options) {
    return printFilteredSchema(schema, serviceList, (n) => !graphql_1.isSpecifiedDirective(n) && !utils_1.isFederationDirective(n), isDefinedType, options);
}
exports.printComposedSdl = printComposedSdl;
function printIntrospectionSchema(schema, options) {
    return printFilteredSchema(schema, [], graphql_1.isSpecifiedDirective, graphql_1.isIntrospectionType, options);
}
exports.printIntrospectionSchema = printIntrospectionSchema;
function isDefinedType(type) {
    return (!graphql_1.isSpecifiedScalarType(type) &&
        !graphql_1.isIntrospectionType(type) &&
        !types_1.isFederationType(type));
}
function printFilteredSchema(schema, serviceList, directiveFilter, typeFilter, options) {
    const directives = [
        ...csdlDirectives_1.default,
        ...schema.getDirectives().filter(directiveFilter),
    ];
    const types = Object.values(schema.getTypeMap())
        .sort((type1, type2) => type1.name.localeCompare(type2.name))
        .filter(typeFilter);
    return ([printSchemaDefinition(schema, serviceList)]
        .concat(directives.map(directive => printDirective(directive, options)), types.map(type => printType(type, options)))
        .filter(Boolean)
        .join('\n\n') + '\n');
}
function printSchemaDefinition(schema, serviceList) {
    const operationTypes = [];
    const queryType = schema.getQueryType();
    if (queryType) {
        operationTypes.push(`  query: ${queryType.name}`);
    }
    const mutationType = schema.getMutationType();
    if (mutationType) {
        operationTypes.push(`  mutation: ${mutationType.name}`);
    }
    const subscriptionType = schema.getSubscriptionType();
    if (subscriptionType) {
        operationTypes.push(`  subscription: ${subscriptionType.name}`);
    }
    return ('schema' +
        printFederationSchemaDirectives(serviceList) +
        `\n{\n${operationTypes.join('\n')}\n}`);
}
function printFederationSchemaDirectives(serviceList) {
    return (serviceList.map(service => `\n  @graph(name: "${service.name}", url: "${service.url}")`).join('') +
        `\n  @composedGraph(version: 1)`);
}
function printType(type, options) {
    if (graphql_1.isScalarType(type)) {
        return printScalar(type, options);
    }
    else if (graphql_1.isObjectType(type)) {
        return printObject(type, options);
    }
    else if (graphql_1.isInterfaceType(type)) {
        return printInterface(type, options);
    }
    else if (graphql_1.isUnionType(type)) {
        return printUnion(type, options);
    }
    else if (graphql_1.isEnumType(type)) {
        return printEnum(type, options);
    }
    else if (graphql_1.isInputObjectType(type)) {
        return printInputObject(type, options);
    }
    throw Error('Unexpected type: ' + type.toString());
}
exports.printType = printType;
function printScalar(type, options) {
    return printDescription(options, type) + `scalar ${type.name}`;
}
function printObject(type, options) {
    const interfaces = type.getInterfaces();
    const implementedInterfaces = interfaces.length
        ? ' implements ' + interfaces.map(i => i.name).join(' & ')
        : '';
    const isExtension = type.extensionASTNodes && type.astNode && !type.astNode.fields;
    return (printDescription(options, type) +
        (isExtension ? 'extend ' : '') +
        `type ${type.name}` +
        implementedInterfaces +
        printFederationTypeDirectives(type) +
        printFields(options, type));
}
function printFederationTypeDirectives(type) {
    var _a;
    const metadata = (_a = type.extensions) === null || _a === void 0 ? void 0 : _a.federation;
    if (!metadata)
        return '';
    const { serviceName: ownerService, keys } = metadata;
    if (!ownerService || !keys)
        return '';
    const _b = keys, _c = ownerService, ownerKeys = _b[_c], restKeys = __rest(_b, [typeof _c === "symbol" ? _c : _c + ""]);
    const ownerEntry = [ownerService, ownerKeys];
    const restEntries = Object.entries(restKeys);
    return (`\n  @owner(graph: "${ownerService}")` +
        [ownerEntry, ...restEntries].map(([service, keys]) => keys
            .map((selections) => `\n  @key(fields: "${printFieldSet(selections)}", graph: "${service}")`)
            .join(''))
            .join(''));
}
function printInterface(type, options) {
    const isExtension = type.extensionASTNodes && type.astNode && !type.astNode.fields;
    return (printDescription(options, type) +
        (isExtension ? 'extend ' : '') +
        `interface ${type.name}` +
        printFields(options, type));
}
function printUnion(type, options) {
    const types = type.getTypes();
    const possibleTypes = types.length ? ' = ' + types.join(' | ') : '';
    return printDescription(options, type) + 'union ' + type.name + possibleTypes;
}
function printEnum(type, options) {
    const values = type
        .getValues()
        .map((value, i) => printDescription(options, value, '  ', !i) +
        '  ' +
        value.name +
        printDeprecated(value));
    return (printDescription(options, type) + `enum ${type.name}` + printBlock(values));
}
function printInputObject(type, options) {
    const fields = Object.values(type.getFields()).map((f, i) => printDescription(options, f, '  ', !i) + '  ' + printInputValue(f));
    return (printDescription(options, type) + `input ${type.name}` + printBlock(fields));
}
function printFields(options, type) {
    var _a, _b;
    const fields = Object.values(type.getFields()).map((f, i) => printDescription(options, f, '  ', !i) +
        '  ' +
        f.name +
        printArgs(options, f.args, '  ') +
        ': ' +
        String(f.type) +
        printDeprecated(f) +
        printFederationFieldDirectives(f, type));
    const isEntity = Boolean((_b = (_a = type.extensions) === null || _a === void 0 ? void 0 : _a.federation) === null || _b === void 0 ? void 0 : _b.keys);
    return printBlock(fields, isEntity);
}
function printWithReducedWhitespace(ast) {
    return graphql_1.print(ast)
        .replace(/\s+/g, ' ')
        .trim();
}
exports.printWithReducedWhitespace = printWithReducedWhitespace;
function printFieldSet(selections) {
    return `{ ${selections.map(printWithReducedWhitespace).join(' ')} }`;
}
function printFederationFieldDirectives(field, parentType) {
    var _a, _b, _c;
    if (!((_a = field.extensions) === null || _a === void 0 ? void 0 : _a.federation))
        return '';
    const { serviceName, requires = [], provides = [], } = field.extensions.federation;
    let printed = '';
    if (serviceName &&
        serviceName !== ((_c = (_b = parentType.extensions) === null || _b === void 0 ? void 0 : _b.federation) === null || _c === void 0 ? void 0 : _c.serviceName)) {
        printed += ` @resolve(graph: "${serviceName}")`;
    }
    if (requires.length > 0) {
        printed += ` @requires(fields: "${printFieldSet(requires)}")`;
    }
    if (provides.length > 0) {
        printed += ` @provides(fields: "${printFieldSet(provides)}")`;
    }
    return printed;
}
function printBlock(items, onNewLine) {
    return items.length !== 0
        ? onNewLine
            ? '\n{\n' + items.join('\n') + '\n}'
            : ' {\n' + items.join('\n') + '\n}'
        : '';
}
function printArgs(options, args, indentation = '') {
    if (args.length === 0) {
        return '';
    }
    if (args.every((arg) => !arg.description)) {
        return '(' + args.map(printInputValue).join(', ') + ')';
    }
    return ('(\n' +
        args
            .map((arg, i) => printDescription(options, arg, '  ' + indentation, !i) +
            '  ' +
            indentation +
            printInputValue(arg))
            .join('\n') +
        '\n' +
        indentation +
        ')');
}
function printInputValue(arg) {
    const defaultAST = graphql_1.astFromValue(arg.defaultValue, arg.type);
    let argDecl = arg.name + ': ' + String(arg.type);
    if (defaultAST) {
        argDecl += ` = ${graphql_1.print(defaultAST)}`;
    }
    return argDecl;
}
function printDirective(directive, options) {
    return (printDescription(options, directive) +
        'directive @' +
        directive.name +
        printArgs(options, directive.args) +
        (directive.isRepeatable ? ' repeatable' : '') +
        ' on ' +
        directive.locations.join(' | '));
}
function printDeprecated(fieldOrEnumVal) {
    if (!fieldOrEnumVal.isDeprecated) {
        return '';
    }
    const reason = fieldOrEnumVal.deprecationReason;
    const reasonAST = graphql_1.astFromValue(reason, graphql_1.GraphQLString);
    if (reasonAST && reason !== graphql_1.DEFAULT_DEPRECATION_REASON) {
        return ' @deprecated(reason: ' + graphql_1.print(reasonAST) + ')';
    }
    return ' @deprecated';
}
function printDescription(options, def, indentation = '', firstInBlock = true) {
    const { description } = def;
    if (description == null) {
        return '';
    }
    if ((options === null || options === void 0 ? void 0 : options.commentDescriptions) === true) {
        return printDescriptionWithComments(description, indentation, firstInBlock);
    }
    const preferMultipleLines = description.length > 70;
    const blockString = printBlockString(description, '', preferMultipleLines);
    const prefix = indentation && !firstInBlock ? '\n' + indentation : indentation;
    return prefix + blockString.replace(/\n/g, '\n' + indentation) + '\n';
}
function printDescriptionWithComments(description, indentation, firstInBlock) {
    const prefix = indentation && !firstInBlock ? '\n' : '';
    const comment = description
        .split('\n')
        .map((line) => indentation + (line !== '' ? '# ' + line : '#'))
        .join('\n');
    return prefix + comment + '\n';
}
function printBlockString(value, indentation = '', preferMultipleLines = false) {
    const isSingleLine = value.indexOf('\n') === -1;
    const hasLeadingSpace = value[0] === ' ' || value[0] === '\t';
    const hasTrailingQuote = value[value.length - 1] === '"';
    const hasTrailingSlash = value[value.length - 1] === '\\';
    const printAsMultipleLines = !isSingleLine ||
        hasTrailingQuote ||
        hasTrailingSlash ||
        preferMultipleLines;
    let result = '';
    if (printAsMultipleLines && !(isSingleLine && hasLeadingSpace)) {
        result += '\n' + indentation;
    }
    result += indentation ? value.replace(/\n/g, '\n' + indentation) : value;
    if (printAsMultipleLines) {
        result += '\n';
    }
    return '"""' + result.replace(/"""/g, '\\"""') + '"""';
}
exports.printBlockString = printBlockString;
//# sourceMappingURL=printComposedSdl.js.map