import globby, { sync } from 'globby';
import unixify from 'unixify';
import { join, extname } from 'path';
import { readFileSync, promises, statSync } from 'fs';
import { parse } from 'graphql';
import { createRequire } from 'module';
import { cwd } from 'process';

const { readFile, stat } = promises;
const DEFAULT_IGNORED_EXTENSIONS = ['spec', 'test', 'd', 'map'];
const DEFAULT_EXTENSIONS = ['gql', 'graphql', 'graphqls', 'ts', 'js'];
const DEFAULT_EXPORT_NAMES = ['schema', 'typeDef', 'typeDefs', 'resolver', 'resolvers'];
const DEFAULT_EXTRACT_EXPORTS_FACTORY = (exportNames) => (fileExport) => {
    if (!fileExport) {
        return null;
    }
    if (fileExport.default) {
        for (const exportName of exportNames) {
            if (fileExport.default[exportName]) {
                return fileExport.default[exportName];
            }
        }
        return fileExport.default;
    }
    for (const exportName of exportNames) {
        if (fileExport[exportName]) {
            return fileExport[exportName];
        }
    }
    return fileExport;
};
function asArray(obj) {
    if (obj instanceof Array) {
        return obj;
    }
    else {
        return [obj];
    }
}
function isDirectorySync(path) {
    try {
        const pathStat = statSync(path);
        return pathStat.isDirectory();
    }
    catch (e) {
        return false;
    }
}
async function isDirectory(path) {
    try {
        const pathStat = await stat(path);
        return pathStat.isDirectory();
    }
    catch (e) {
        return false;
    }
}
function scanForFilesSync(globStr, globOptions = {}) {
    return sync(globStr, { absolute: true, ...globOptions });
}
function formatExtension(extension) {
    return extension.charAt(0) === '.' ? extension : `.${extension}`;
}
function buildGlob(basePath, extensions = [], ignoredExtensions = [], recursive) {
    const ignored = ignoredExtensions.length > 0 ? `!(${ignoredExtensions.map(e => `*${formatExtension(e)}`).join('|')})` : '*';
    const ext = extensions.map(e => `*${formatExtension(e)}`).join('|');
    return `${basePath}${recursive ? '/**' : ''}/${ignored}+(${ext})`;
}
const LoadFilesDefaultOptions = {
    ignoredExtensions: DEFAULT_IGNORED_EXTENSIONS,
    extensions: DEFAULT_EXTENSIONS,
    useRequire: false,
    requireMethod: null,
    globOptions: {
        absolute: true,
        cwd: cwd(),
    },
    exportNames: DEFAULT_EXPORT_NAMES,
    recursive: true,
    ignoreIndex: false,
};
/**
 * Synchronously loads files using the provided glob pattern.
 * @param pattern Glob pattern or patterns to use when loading files
 * @param options Additional options
 */
function loadFilesSync(pattern, options = LoadFilesDefaultOptions) {
    var _a, _b;
    const execOptions = { ...LoadFilesDefaultOptions, ...options };
    const relevantPaths = scanForFilesSync(asArray(pattern).map(path => isDirectorySync(path)
        ? buildGlob(unixify(path), execOptions.extensions, execOptions.ignoredExtensions, execOptions.recursive)
        : unixify(path)), options.globOptions);
    const extractExports = execOptions.extractExports || DEFAULT_EXTRACT_EXPORTS_FACTORY((_a = execOptions.exportNames) !== null && _a !== void 0 ? _a : []);
    const requireMethod = execOptions.requireMethod || createRequire(join(((_b = options === null || options === void 0 ? void 0 : options.globOptions) === null || _b === void 0 ? void 0 : _b.cwd) || cwd(), 'noop.js'));
    return relevantPaths
        .map(path => {
        if (!checkExtension(path, options)) {
            return null;
        }
        if (isIndex(path, execOptions.extensions) && options.ignoreIndex) {
            return false;
        }
        const extension = extname(path);
        if (extension === formatExtension('js') || extension === formatExtension('ts') || execOptions.useRequire) {
            const fileExports = requireMethod(path);
            const extractedExport = extractExports(fileExports);
            return extractedExport;
        }
        else {
            const maybeSDL = readFileSync(path, { encoding: 'utf-8' });
            return tryToParse(maybeSDL);
        }
    })
        .filter(v => v);
}
async function scanForFiles(globStr, globOptions = {}) {
    return globby(globStr, { absolute: true, ...globOptions });
}
const checkExtension = (path, { extensions, ignoredExtensions }) => {
    if (ignoredExtensions) {
        for (const ignoredExtension of ignoredExtensions) {
            if (path.endsWith(formatExtension(ignoredExtension))) {
                return false;
            }
        }
    }
    if (!extensions) {
        return true;
    }
    for (const extension of extensions) {
        const formattedExtension = formatExtension(extension);
        if (path.endsWith(formattedExtension)) {
            if (ignoredExtensions) {
                for (const ignoredExtension of ignoredExtensions) {
                    const formattedIgnoredExtension = formatExtension(ignoredExtension);
                    if (path.endsWith(formattedIgnoredExtension + formattedExtension)) {
                        return false;
                    }
                }
            }
            return true;
        }
    }
    return false;
};
/**
 * Asynchronously loads files using the provided glob pattern.
 * @param pattern Glob pattern or patterns to use when loading files
 * @param options Additional options
 */
async function loadFiles(pattern, options = LoadFilesDefaultOptions) {
    var _a;
    const execOptions = { ...LoadFilesDefaultOptions, ...options };
    const relevantPaths = await scanForFiles(await Promise.all(asArray(pattern).map(async (path) => (await isDirectory(path))
        ? buildGlob(unixify(path), execOptions.extensions, execOptions.ignoredExtensions, execOptions.recursive)
        : unixify(path))), options.globOptions);
    const extractExports = execOptions.extractExports || DEFAULT_EXTRACT_EXPORTS_FACTORY((_a = execOptions.exportNames) !== null && _a !== void 0 ? _a : []);
    const defaultRequireMethod = (path) => import(path).catch(importError => {
        var _a;
        const cwdRequire = createRequire(join(((_a = options === null || options === void 0 ? void 0 : options.globOptions) === null || _a === void 0 ? void 0 : _a.cwd) || cwd(), 'noop.js'));
        try {
            return cwdRequire(path);
        }
        catch (e) {
            throw importError;
        }
    });
    const requireMethod = execOptions.requireMethod || defaultRequireMethod;
    return Promise.all(relevantPaths
        .filter(path => checkExtension(path, options) && !(isIndex(path, execOptions.extensions) && options.ignoreIndex))
        .map(async (path) => {
        const extension = extname(path);
        if (extension === formatExtension('js') || extension === formatExtension('ts') || execOptions.useRequire) {
            const fileExports = await requireMethod(path);
            const extractedExport = extractExports(fileExports);
            return extractedExport;
        }
        else {
            const maybeSDL = await readFile(path, { encoding: 'utf-8' });
            return tryToParse(maybeSDL);
        }
    }));
}
function isIndex(path, extensions = []) {
    const IS_INDEX = /(\/|\\)index\.[^\/\\]+$/i; // (/ or \) AND `index.` AND (everything except \ and /)(end of line)
    return IS_INDEX.test(path) && extensions.some(ext => path.endsWith(formatExtension(ext)));
}
function tryToParse(maybeSDL) {
    try {
        return parse(maybeSDL);
    }
    catch (e) {
        return maybeSDL;
    }
}

export { loadFiles, loadFilesSync };
