import fs from 'node:fs';
import module from 'node:module';
import path from 'node:path';
import isNodeCoreModule from '@nolyfill/is-core-module';
import debug from 'debug';
import { createPathsMatcher, getTsconfig } from 'get-tsconfig';
import { isBunModule } from 'is-bun-module';
import { stableHash } from 'stable-hash';
import { globSync, isDynamicPattern } from 'tinyglobby';
import { ResolverFactory } from 'unrs-resolver';
const IMPORTER_NAME = 'eslint-import-resolver-typescript';
const log = debug(IMPORTER_NAME);
export const defaultConditionNames = [
    'types',
    'import',
    'esm2020',
    'es2020',
    'es2015',
    'require',
    'node',
    'node-addons',
    'browser',
    'default',
];
export const defaultExtensions = [
    '.ts',
    '.tsx',
    '.d.ts',
    '.js',
    '.jsx',
    '.json',
    '.node',
];
export const defaultExtensionAlias = {
    '.js': [
        '.ts',
        '.tsx',
        '.d.ts',
        '.js',
    ],
    '.jsx': ['.tsx', '.d.ts', '.jsx'],
    '.cjs': ['.cts', '.d.cts', '.cjs'],
    '.mjs': ['.mts', '.d.mts', '.mjs'],
};
export const defaultMainFields = [
    'types',
    'typings',
    'fesm2020',
    'fesm2015',
    'esm2020',
    'es2020',
    'module',
    'jsnext:main',
    'main',
];
export const interfaceVersion = 2;
const JS_EXT_PATTERN = /\.(?:[cm]js|jsx?)$/;
const RELATIVE_PATH_PATTERN = /^\.{1,2}(?:\/.*)?$/;
let previousOptionsHash;
let optionsHash;
let cachedOptions;
let cachedCwd;
let mappersCachedOptions;
let mappers = [];
let resolverCachedOptions;
let cachedResolver;
export function resolve(source, file, options, resolver) {
    if (!cachedOptions ||
        previousOptionsHash !== (optionsHash = stableHash(options))) {
        previousOptionsHash = optionsHash;
        cachedOptions = {
            ...options,
            conditionNames: options?.conditionNames ?? defaultConditionNames,
            extensions: options?.extensions ?? defaultExtensions,
            extensionAlias: options?.extensionAlias ?? defaultExtensionAlias,
            mainFields: options?.mainFields ?? defaultMainFields,
        };
    }
    if (!resolver) {
        if (!cachedResolver || resolverCachedOptions !== cachedOptions) {
            cachedResolver = new ResolverFactory(cachedOptions);
            resolverCachedOptions = cachedOptions;
        }
        resolver = cachedResolver;
    }
    log('looking for', source, 'in', file);
    source = removeQuerystring(source);
    if (isNodeCoreModule(source) || isBunModule(source)) {
        log('matched core:', source);
        return {
            found: true,
            path: null,
        };
    }
    if (process.versions.pnp && source === 'pnpapi') {
        return {
            found: true,
            path: module.findPnpApi(file).resolveToUnqualified(source, file, {
                considerBuiltins: false,
            }),
        };
    }
    initMappers(cachedOptions);
    let mappedPaths = getMappedPaths(source, file, cachedOptions.extensions, true);
    if (mappedPaths.length > 0) {
        log('matched ts path:', ...mappedPaths);
    }
    else {
        mappedPaths = [source];
    }
    let foundNodePath;
    for (const mappedPath of mappedPaths) {
        try {
            const resolved = resolver.sync(path.dirname(path.resolve(file)), mappedPath);
            if (resolved.path) {
                foundNodePath = resolved.path;
                break;
            }
        }
        catch {
            log('failed to resolve with', mappedPath);
        }
    }
    if ((JS_EXT_PATTERN.test(foundNodePath) ||
        (cachedOptions.alwaysTryTypes && !foundNodePath)) &&
        !/^@types[/\\]/.test(source) &&
        !path.isAbsolute(source) &&
        !source.startsWith('.')) {
        const definitelyTyped = resolve('@types' + path.sep + mangleScopedPackage(source), file, options);
        if (definitelyTyped.found) {
            return definitelyTyped;
        }
    }
    if (foundNodePath) {
        log('matched node path:', foundNodePath);
        return {
            found: true,
            path: foundNodePath,
        };
    }
    log("didn't find ", source);
    return {
        found: false,
    };
}
export function createTypeScriptImportResolver(options) {
    const resolver = new ResolverFactory({
        ...options,
        conditionNames: options?.conditionNames ?? defaultConditionNames,
        extensions: options?.extensions ?? defaultExtensions,
        extensionAlias: options?.extensionAlias ?? defaultExtensionAlias,
        mainFields: options?.mainFields ?? defaultMainFields,
    });
    return {
        interfaceVersion: 3,
        name: IMPORTER_NAME,
        resolve(source, file) {
            return resolve(source, file, options, resolver);
        },
    };
}
function removeQuerystring(id) {
    const querystringIndex = id.lastIndexOf('?');
    if (querystringIndex !== -1) {
        return id.slice(0, querystringIndex);
    }
    return id;
}
const isFile = (path) => {
    try {
        return !!(path && fs.statSync(path, { throwIfNoEntry: false })?.isFile());
    }
    catch {
        return false;
    }
};
const isModule = (modulePath) => !!modulePath && isFile(path.resolve(modulePath, 'package.json'));
function getMappedPaths(source, file, extensions = defaultExtensions, retry) {
    const originalExtensions = extensions;
    extensions = ['', ...extensions];
    let paths = [];
    if (RELATIVE_PATH_PATTERN.test(source)) {
        const resolved = path.resolve(path.dirname(file), source);
        if (isFile(resolved)) {
            paths = [resolved];
        }
    }
    else {
        let mapperFns = mappers
            .filter(({ files }) => files.has(file))
            .map(({ mapperFn }) => mapperFn);
        if (mapperFns.length === 0) {
            mapperFns = mappers
                .map(mapper => ({
                mapperFn: mapper.mapperFn,
                counter: equalChars(path.dirname(file), path.dirname(mapper.path)),
            }))
                .sort((a, b) => b.counter - a.counter)
                .map(({ mapperFn }) => mapperFn);
        }
        paths = mapperFns
            .map(mapperFn => mapperFn(source).map(item => [
            ...extensions.map(ext => `${item}${ext}`),
            ...originalExtensions.map(ext => `${item}/index${ext}`),
        ]))
            .flat(2)
            .map(toNativePathSeparator)
            .filter(mappedPath => {
            try {
                const stat = fs.statSync(mappedPath, { throwIfNoEntry: false });
                if (stat === undefined)
                    return false;
                if (stat.isFile())
                    return true;
                if (stat.isDirectory()) {
                    return isModule(mappedPath);
                }
            }
            catch {
                return false;
            }
            return false;
        });
    }
    if (retry && paths.length === 0) {
        const isJs = JS_EXT_PATTERN.test(source);
        if (isJs) {
            const jsExt = path.extname(source);
            const tsExt = jsExt.replace('js', 'ts');
            const basename = source.replace(JS_EXT_PATTERN, '');
            let resolved = getMappedPaths(basename + tsExt, file);
            if (resolved.length === 0 && jsExt === '.js') {
                const tsxExt = jsExt.replace('js', 'tsx');
                resolved = getMappedPaths(basename + tsxExt, file);
            }
            if (resolved.length === 0) {
                resolved = getMappedPaths(basename + '.d' + (tsExt === '.tsx' ? '.ts' : tsExt), file);
            }
            if (resolved.length > 0) {
                return resolved;
            }
        }
        for (const ext of extensions) {
            const mappedPaths = isJs ? [] : getMappedPaths(source + ext, file);
            const resolved = mappedPaths.length > 0
                ? mappedPaths
                : getMappedPaths(source + `/index${ext}`, file);
            if (resolved.length > 0) {
                return resolved;
            }
        }
    }
    return paths;
}
function initMappers(options) {
    if (mappers.length > 0 &&
        mappersCachedOptions === options &&
        cachedCwd === process.cwd()) {
        return;
    }
    cachedCwd = process.cwd();
    const configPaths = (typeof options.project === 'string'
        ? [options.project]
        :
            Array.isArray(options.project)
                ? options.project
                : [cachedCwd])
        .map(config => replacePathSeparator(config, path.sep, path.posix.sep));
    const defaultInclude = ['**/*'];
    const defaultIgnore = ['**/node_modules/**'];
    const projectPaths = [
        ...new Set([
            ...configPaths
                .filter(p => !isDynamicPattern(p))
                .map(p => path.resolve(process.cwd(), p)),
            ...globSync(configPaths.filter(path => isDynamicPattern(path)), {
                absolute: true,
                dot: true,
                expandDirectories: false,
                ignore: defaultIgnore,
            }),
        ]),
    ];
    mappers = projectPaths
        .map(projectPath => {
        let tsconfigResult;
        if (isFile(projectPath)) {
            const { dir, base } = path.parse(projectPath);
            tsconfigResult = getTsconfig(dir, base);
        }
        else {
            tsconfigResult = getTsconfig(projectPath);
        }
        if (!tsconfigResult) {
            return;
        }
        const mapperFn = createPathsMatcher(tsconfigResult);
        if (!mapperFn) {
            return;
        }
        const files = tsconfigResult.config.files == null &&
            tsconfigResult.config.include == null
            ?
                globSync(defaultInclude, {
                    absolute: true,
                    cwd: path.dirname(tsconfigResult.path),
                    dot: true,
                    ignore: [
                        ...(tsconfigResult.config.exclude ?? []),
                        ...defaultIgnore,
                    ],
                })
            : [
                ...(tsconfigResult.config.files != null &&
                    tsconfigResult.config.files.length > 0
                    ? tsconfigResult.config.files.map(file => path.normalize(path.resolve(path.dirname(tsconfigResult.path), file)))
                    : []),
                ...(tsconfigResult.config.include != null &&
                    tsconfigResult.config.include.length > 0
                    ? globSync(tsconfigResult.config.include, {
                        absolute: true,
                        cwd: path.dirname(tsconfigResult.path),
                        dot: true,
                        ignore: [
                            ...(tsconfigResult.config.exclude ?? []),
                            ...defaultIgnore,
                        ],
                    })
                    : []),
            ];
        return {
            path: toNativePathSeparator(tsconfigResult.path),
            files: new Set(files.map(toNativePathSeparator)),
            mapperFn,
        };
    })
        .filter(Boolean);
    mappersCachedOptions = options;
}
function mangleScopedPackage(moduleName) {
    if (moduleName.startsWith('@')) {
        const replaceSlash = moduleName.replace(path.sep, '__');
        if (replaceSlash !== moduleName) {
            return replaceSlash.slice(1);
        }
    }
    return moduleName;
}
function replacePathSeparator(p, from, to) {
    return from === to ? p : p.replaceAll(from, to);
}
function toNativePathSeparator(p) {
    return replacePathSeparator(p, path[process.platform === 'win32' ? 'posix' : 'win32'].sep, path[process.platform === 'win32' ? 'win32' : 'posix'].sep);
}
function equalChars(a, b) {
    if (a.length === 0 || b.length === 0) {
        return 0;
    }
    let i = 0;
    const length = Math.min(a.length, b.length);
    while (i < length && a.charAt(i) === b.charAt(i)) {
        i += 1;
    }
    return i;
}
//# sourceMappingURL=index.js.map