import { GraphQLSchema, GraphQLNonNull, GraphQLObjectType, GraphQLString, GraphQLEnumType } from 'graphql';
import { getWhitelistBlacklistConfig, Mutate } from './graphql.js';
import { capitalize, capitalizeProtoName, getUseSubscriptions, getServiceName } from './utils.js';
import { getTyping } from './registry.js';
const typeCache = new Map();
const subscriptionAction = new GraphQLEnumType({
    name: 'SubscriptionAction',
    values: {
        CREATED: { value: 'CREATED' },
        UPDATED: { value: 'UPDATED' },
        DELETED: { value: 'DELETED' }
    }
});
const subscriptionInput = {
    action: {
        type: subscriptionAction
    }
};
const subscriptionOutput = new GraphQLObjectType({
    name: 'SubscriptionOutput',
    fields: {
        id: {
            type: GraphQLString
        }
    }
});
export const getGQLSchema = (method) => {
    const fields = {};
    const responseTyping = getTyping(method.outputType);
    if (!responseTyping) {
        throw Error('Method doesn\'t have registered typings: ' + method.outputType);
    }
    if (method.outputType !== '.google.protobuf.Empty') {
        fields['details'] = {
            type: responseTyping.output,
        };
    }
    const outName = 'Proto' + capitalizeProtoName(method.outputType);
    let out = typeCache.get(outName);
    if (!out) {
        out = new GraphQLObjectType({
            name: outName,
            fields,
        });
        typeCache.set(outName, out);
    }
    const typing = getTyping(method.inputType);
    if (!typing) {
        throw Error('Method doesn\'t have registered typings: ' + method.inputType);
    }
    return {
        type: out,
        args: method.inputType === '.google.protobuf.Empty' ? undefined : {
            input: {
                type: new GraphQLNonNull(typing.input)
            }
        }
    };
};
export const getGQLSchemas = (service) => {
    return service.method?.reduce((obj, method) => {
        obj[method.name] = getGQLSchema(method);
        return obj;
    }, {});
};
const namespaceResolverSchemaRegistry = new Map();
const subscriptionFields = {};
export const registerResolverSchema = (namespace, name, schema, mutation = false, subspace = undefined, config) => {
    if (!namespaceResolverSchemaRegistry.has(namespace)) {
        namespaceResolverSchemaRegistry.set(namespace, new Map());
    }
    if (subspace && config[subspace]) {
        const blacklistMethods = config[subspace].methods.blacklist;
        if (blacklistMethods.includes(name)) {
            return;
        }
    }
    if (!namespaceResolverSchemaRegistry.get(namespace).has(mutation)) {
        namespaceResolverSchemaRegistry.get(namespace).set(mutation, new Map());
    }
    let space = namespaceResolverSchemaRegistry.get(namespace).get(mutation);
    if (subspace) {
        if (!space.has(subspace)) {
            space.set(subspace, new Map());
        }
        space = space.get(subspace);
    }
    if (space.has(name)) {
        if (subspace) {
            throw new Error(`Namespace "${namespace}"."${subspace}" already contains a schema: ${name} (mutation: ${mutation})`);
        }
        else {
            throw new Error(`Namespace "${namespace}" already contains a schema: ${name} (mutation: ${mutation})`);
        }
    }
    // register create, update and upsert with Mutate
    if (Mutate.indexOf(name) > -1) {
        name = 'Mutate';
    }
    space.set(name, schema);
};
export const generateSchema = (setup) => {
    const queryFields = {};
    const mutationFields = {};
    const subFields = {};
    setup.forEach(s => {
        if (!namespaceResolverSchemaRegistry.has(s.namespace)) {
            throw new Error(`Namespace "${s.namespace}" has no registered schemas`);
        }
        if (namespaceResolverSchemaRegistry.get(s.namespace).has(false)) {
            const fields = {};
            namespaceResolverSchemaRegistry.get(s.namespace).get(false).forEach((value, key) => {
                if (value instanceof Map) {
                    const capitalName = capitalizeProtoName(key);
                    fields[key] = {
                        type: new GraphQLNonNull(new GraphQLObjectType({
                            name: s.prefix + capitalName + 'Query',
                            fields: Object.fromEntries(value),
                        }))
                    };
                }
                else {
                    fields[key] = value;
                }
            });
            queryFields[s.namespace] = {
                type: new GraphQLNonNull(new GraphQLObjectType({
                    name: s.prefix + 'Query',
                    fields
                }))
            };
        }
        if (namespaceResolverSchemaRegistry.get(s.namespace).has(true)) {
            const fields = {};
            namespaceResolverSchemaRegistry.get(s.namespace).get(true).forEach((value, key) => {
                if (value instanceof Map) {
                    const capitalName = capitalizeProtoName(key);
                    fields[key] = {
                        type: new GraphQLNonNull(new GraphQLObjectType({
                            name: s.prefix + capitalName + 'Mutation',
                            fields: Object.fromEntries(value),
                        }))
                    };
                }
                else {
                    fields[key] = value;
                }
            });
            mutationFields[s.namespace] = {
                type: new GraphQLNonNull(new GraphQLObjectType({
                    name: s.prefix + 'Mutation',
                    fields
                }))
            };
        }
        if (s.namespace in subscriptionFields) {
            for (let [k, v] of Object.entries(subscriptionFields[s.namespace])) {
                subFields[k] = v;
            }
        }
    });
    const config = {};
    if (Object.keys(queryFields).length > 0) {
        config.query = new GraphQLObjectType({
            name: 'Query',
            fields: queryFields
        });
    }
    if (Object.keys(mutationFields).length > 0) {
        config.mutation = new GraphQLObjectType({
            name: 'Mutation',
            fields: mutationFields
        });
    }
    if (Object.keys(subFields).length > 0) {
        config.subscription = new GraphQLObjectType({
            name: 'Subscription',
            fields: subFields
        });
    }
    return new GraphQLSchema({
        ...config,
    });
};
export const generateSubServiceSchemas = (subServices, config, namespace, prefix) => {
    subServices.forEach((meta) => {
        meta.fileDescriptor.service.forEach(service => {
            if (service.name) {
                const subName = getServiceName(service.name);
                const { mutations, queries } = getWhitelistBlacklistConfig(service, config, meta, subName);
                const schemas = getGQLSchemas(service);
                Object.keys(schemas).forEach(key => {
                    registerResolverSchema(config.root ? subName : namespace, key, schemas[key], !queries.has(key) && mutations.has(key), config.root ? undefined : subName, config);
                });
            }
        });
        if (getUseSubscriptions()) {
            Object.entries(meta.options?.messages || {}).forEach(([messageName, option]) => {
                if (option.options && 'kafka_subscriber' in option.options) {
                    const fieldName = namespace + capitalize(option.options.kafka_subscriber.plural);
                    const typing = getTyping('.' + meta.fileDescriptor.package + '.' + messageName);
                    if (typing) {
                        if (!(namespace in subscriptionFields)) {
                            subscriptionFields[namespace] = {};
                        }
                        subscriptionFields[namespace][fieldName] = {
                            // TODO Implement user lookup
                            // type: typing?.output!,
                            type: subscriptionOutput,
                            args: subscriptionInput
                        };
                    }
                }
            });
        }
    });
    if (config.root) {
        return generateSchema(subServices.flatMap(meta => {
            return meta.fileDescriptor.service.map(service => {
                if (service.name) {
                    const subName = getServiceName(service.name);
                    return {
                        prefix: prefix + subName.substring(0, 1).toUpperCase() + subName.substring(1).toLowerCase(),
                        namespace: subName
                    };
                }
                return undefined;
            }).filter(Boolean);
        }));
    }
    return generateSchema([{ prefix, namespace }]);
};
//# sourceMappingURL=schema.js.map