import _ from 'lodash';
import { asArray } from '@graphql-tools/utils';
import micromatch from 'micromatch';

function chainFunctions(funcs) {
    if (funcs.length === 1) {
        return funcs[0];
    }
    return funcs.reduce((a, b) => (...args) => a(b(...args)));
}

function isScalarTypeConfiguration(config) {
    return config && 'serialize' in config && 'parseLiteral' in config;
}
function resolveRelevantMappings(resolvers, path) {
    if (!resolvers) {
        return [];
    }
    const [typeNameOrGlob, fieldNameOrGlob] = path.split('.');
    const isTypeMatch = micromatch.matcher(typeNameOrGlob);
    let fixedFieldGlob = fieldNameOrGlob;
    // convert single value OR `{singleField}` to `singleField` as matching will fail otherwise
    if (fixedFieldGlob.includes('{') && !fixedFieldGlob.includes(',')) {
        fixedFieldGlob = fieldNameOrGlob.replace('{', '').replace('}', '');
    }
    fixedFieldGlob = fixedFieldGlob.replace(', ', ',').trim();
    const isFieldMatch = micromatch.matcher(fixedFieldGlob);
    const mappings = [];
    for (const typeName in resolvers) {
        if (!isTypeMatch(typeName)) {
            continue;
        }
        if (isScalarTypeConfiguration(resolvers[typeName])) {
            continue;
        }
        const fieldMap = resolvers[typeName];
        if (!fieldMap) {
            return [];
        }
        for (const field in fieldMap) {
            if (!isFieldMatch(field)) {
                continue;
            }
            const resolvedPath = `${typeName}.${field}`;
            if (resolvers[typeName] && resolvers[typeName][field]) {
                if (resolvers[typeName][field].subscribe) {
                    mappings.push(resolvedPath + '.subscribe');
                }
                if (resolvers[typeName][field].resolve) {
                    mappings.push(resolvedPath + '.resolve');
                }
                if (typeof resolvers[typeName][field] === 'function') {
                    mappings.push(resolvedPath);
                }
            }
        }
    }
    return mappings;
}
/**
 * Wraps the resolvers object with the resolvers composition objects.
 * Implemented as a simple and basic middleware mechanism.
 *
 * @param resolvers - resolvers object
 * @param mapping - resolvers composition mapping
 * @hidden
 */
function composeResolvers(resolvers, mapping = {}) {
    const mappingResult = {};
    for (const resolverPath in mapping) {
        const resolverPathMapping = mapping[resolverPath];
        if (resolverPathMapping instanceof Array || typeof resolverPathMapping === 'function') {
            const composeFns = resolverPathMapping;
            const relevantFields = resolveRelevantMappings(resolvers, resolverPath);
            for (const path of relevantFields) {
                mappingResult[path] = asArray(composeFns);
            }
        }
        else if (resolverPathMapping) {
            for (const fieldName in resolverPathMapping) {
                const composeFns = resolverPathMapping[fieldName];
                const relevantFields = resolveRelevantMappings(resolvers, resolverPath + '.' + fieldName);
                for (const path of relevantFields) {
                    mappingResult[path] = asArray(composeFns);
                }
            }
        }
    }
    for (const path in mappingResult) {
        const fns = chainFunctions([...asArray(mappingResult[path]), () => _.get(resolvers, path)]);
        _.set(resolvers, path, fns());
    }
    return resolvers;
}

export { composeResolvers };
