import { defaultOverrides } from '../server/require-hook';
import { BARREL_OPTIMIZATION_PREFIX } from '../shared/lib/constants';
import path from '../shared/lib/isomorphic/path';
import { NODE_BASE_ESM_RESOLVE_OPTIONS, NODE_BASE_RESOLVE_OPTIONS, NODE_ESM_RESOLVE_OPTIONS, NODE_RESOLVE_OPTIONS } from './webpack-config';
import { isWebpackBundledLayer, shouldUseReactServerCondition } from './utils';
import { normalizePathSep } from '../shared/lib/page-path/normalize-path-sep';
const reactPackagesRegex = /^(react|react-dom|react-server-dom-webpack)($|\/)/;
const pathSeparators = '[/\\\\]';
const optionalEsmPart = `((${pathSeparators}esm)?${pathSeparators})`;
const externalFileEnd = '(\\.external(\\.js)?)$';
const nextDist = `next${pathSeparators}dist`;
const externalPattern = new RegExp(`${nextDist}${optionalEsmPart}.*${externalFileEnd}`);
const nodeModulesRegex = /node_modules[/\\].*\.[mc]?js$/;
export function isResourceInPackages(resource, packageNames, packageDirMapping) {
    if (!packageNames) return false;
    return packageNames.some((p)=>packageDirMapping && packageDirMapping.has(p) ? resource.startsWith(packageDirMapping.get(p) + path.sep) : resource.includes(path.sep + path.join('node_modules', p.replace(/\//g, path.sep)) + path.sep));
}
export async function resolveExternal(dir, esmExternalsConfig, context, request, isEsmRequested, getResolve, isLocalCallback, baseResolveCheck = true, esmResolveOptions = NODE_ESM_RESOLVE_OPTIONS, nodeResolveOptions = NODE_RESOLVE_OPTIONS, baseEsmResolveOptions = NODE_BASE_ESM_RESOLVE_OPTIONS, baseResolveOptions = NODE_BASE_RESOLVE_OPTIONS) {
    const esmExternals = !!esmExternalsConfig;
    const looseEsmExternals = esmExternalsConfig === 'loose';
    let res = null;
    let isEsm = false;
    const preferEsmOptions = esmExternals && isEsmRequested ? [
        true,
        false
    ] : [
        false
    ];
    for (const preferEsm of preferEsmOptions){
        const resolveOptions = preferEsm ? esmResolveOptions : nodeResolveOptions;
        const resolve = getResolve(resolveOptions);
        // Resolve the import with the webpack provided context, this
        // ensures we're resolving the correct version when multiple
        // exist.
        try {
            ;
            [res, isEsm] = await resolve(context, request);
        } catch (err) {
            res = null;
        }
        if (!res) {
            continue;
        }
        // ESM externals can only be imported (and not required).
        // Make an exception in loose mode.
        if (!isEsmRequested && isEsm && !looseEsmExternals) {
            continue;
        }
        if (isLocalCallback) {
            return {
                localRes: isLocalCallback(res)
            };
        }
        // Bundled Node.js code is relocated without its node_modules tree.
        // This means we need to make sure its request resolves to the same
        // package that'll be available at runtime. If it's not identical,
        // we need to bundle the code (even if it _should_ be external).
        if (baseResolveCheck) {
            let baseRes;
            let baseIsEsm;
            try {
                const baseResolve = getResolve(isEsm ? baseEsmResolveOptions : baseResolveOptions);
                [baseRes, baseIsEsm] = await baseResolve(dir, request);
            } catch (err) {
                baseRes = null;
                baseIsEsm = false;
            }
            // Same as above: if the package, when required from the root,
            // would be different from what the real resolution would use, we
            // cannot externalize it.
            // if request is pointing to a symlink it could point to the same file,
            // the resolver will resolve symlinks so this is handled
            if (baseRes !== res || isEsm !== baseIsEsm) {
                res = null;
                continue;
            }
        }
        break;
    }
    return {
        res,
        isEsm
    };
}
export function makeExternalHandler({ config, optOutBundlingPackageRegex, transpiledPackages, dir }) {
    var _config_experimental;
    let resolvedExternalPackageDirs;
    const looseEsmExternals = ((_config_experimental = config.experimental) == null ? void 0 : _config_experimental.esmExternals) === 'loose';
    return async function handleExternals(context, request, dependencyType, layer, getResolve) {
        // We need to externalize internal requests for files intended to
        // not be bundled.
        const isLocal = request.startsWith('.') || // Always check for unix-style path, as webpack sometimes
        // normalizes as posix.
        path.posix.isAbsolute(request) || // When on Windows, we also want to check for Windows-specific
        // absolute paths.
        process.platform === 'win32' && path.win32.isAbsolute(request);
        // make sure import "next" shows a warning when imported
        // in pages/components
        if (request === 'next') {
            return `commonjs next/dist/lib/import-next-warning`;
        }
        const isAppLayer = isWebpackBundledLayer(layer);
        // Relative requires don't need custom resolution, because they
        // are relative to requests we've already resolved here.
        // Absolute requires (require('/foo')) are extremely uncommon, but
        // also have no need for customization as they're already resolved.
        if (!isLocal) {
            if (/^next$/.test(request)) {
                return `commonjs ${request}`;
            }
            if (reactPackagesRegex.test(request) && !isAppLayer) {
                return `commonjs ${request}`;
            }
            // Handle Bun builtins as external modules
            if (request === 'bun' || request.startsWith('bun:')) {
                return `commonjs ${request}`;
            }
            const notExternalModules = /^(?:private-next-pages\/|next\/(?:dist\/pages\/|(?:app|cache|document|link|form|head|image|legacy\/image|constants|dynamic|script|navigation|headers|router|compat\/router|server)$)|string-hash|private-next-rsc-action-validate|private-next-rsc-action-client-wrapper|private-next-rsc-server-reference|private-next-rsc-cache-wrapper|private-next-rsc-track-dynamic-import$)/;
            if (notExternalModules.test(request)) {
                return;
            }
        }
        // @swc/helpers should not be external as it would
        // require hoisting the package which we can't rely on
        if (request.includes('@swc/helpers')) {
            return;
        }
        // BARREL_OPTIMIZATION_PREFIX is a special marker that tells Next.js to
        // optimize the import by removing unused exports. This has to be compiled.
        if (request.startsWith(BARREL_OPTIMIZATION_PREFIX)) {
            return;
        }
        // When in esm externals mode, and using import, we resolve with
        // ESM resolving options.
        // Also disable esm request when appDir is enabled
        const isEsmRequested = dependencyType === 'esm';
        // Don't bundle @vercel/og nodejs bundle for nodejs runtime.
        // TODO-APP: bundle route.js with different layer that externals common node_module deps.
        // Make sure @vercel/og is loaded as ESM for Node.js runtime
        if (shouldUseReactServerCondition(layer) && request === 'next/dist/compiled/@vercel/og/index.node.js') {
            return `module ${request}`;
        }
        // Specific Next.js imports that should remain external
        // TODO-APP: Investigate if we can remove this.
        if (request.startsWith('next/dist/')) {
            // Non external that needs to be transpiled
            // Image loader needs to be transpiled
            if (/^next[\\/]dist[\\/]shared[\\/]lib[\\/]image-loader/.test(request)) {
                return;
            }
            if (/^next[\\/]dist[\\/]compiled[\\/]next-server/.test(request)) {
                return `commonjs ${request}`;
            }
            if (/^next[\\/]dist[\\/]shared[\\/](?!lib[\\/]router[\\/]router)/.test(request) || /^next[\\/]dist[\\/]compiled[\\/].*\.c?js$/.test(request)) {
                return `commonjs ${request}`;
            }
            if (/^next[\\/]dist[\\/]esm[\\/]shared[\\/](?!lib[\\/]router[\\/]router)/.test(request) || /^next[\\/]dist[\\/]compiled[\\/].*\.mjs$/.test(request)) {
                return `module ${request}`;
            }
            return resolveNextExternal(request);
        }
        // TODO-APP: Let's avoid this resolve call as much as possible, and eventually get rid of it.
        const resolveResult = await resolveExternal(dir, config.experimental.esmExternals, context, request, isEsmRequested, getResolve, isLocal ? resolveNextExternal : undefined);
        if ('localRes' in resolveResult) {
            return resolveResult.localRes;
        }
        // Forcedly resolve the styled-jsx installed by next.js,
        // since `resolveExternal` cannot find the styled-jsx dep with pnpm
        if (request === 'styled-jsx/style') {
            resolveResult.res = defaultOverrides['styled-jsx/style'];
        }
        const { res, isEsm } = resolveResult;
        // If the request cannot be resolved we need to have
        // webpack "bundle" it so it surfaces the not found error.
        if (!res) {
            return;
        }
        const isOptOutBundling = optOutBundlingPackageRegex.test(res);
        // Apply bundling rules to all app layers.
        // Since handleExternals only handle the server layers, we don't need to exclude client here
        if (!isOptOutBundling && isAppLayer) {
            return;
        }
        // ESM externals can only be imported (and not required).
        // Make an exception in loose mode.
        if (!isEsmRequested && isEsm && !looseEsmExternals && !isLocal) {
            throw Object.defineProperty(new Error(`ESM packages (${request}) need to be imported. Use 'import' to reference the package instead. https://nextjs.org/docs/messages/import-esm-externals`), "__NEXT_ERROR_CODE", {
                value: "E310",
                enumerable: false,
                configurable: true
            });
        }
        const externalType = isEsm ? 'module' : 'commonjs';
        // Default pages have to be transpiled
        if (// This is the @babel/plugin-transform-runtime "helpers: true" option
        /node_modules[/\\]@babel[/\\]runtime[/\\]/.test(res)) {
            return;
        }
        // Webpack itself has to be compiled because it doesn't always use module relative paths
        if (/node_modules[/\\]webpack/.test(res) || /node_modules[/\\]css-loader/.test(res)) {
            return;
        }
        // If a package should be transpiled by Next.js, we skip making it external.
        // It doesn't matter what the extension is, as we'll transpile it anyway.
        if (transpiledPackages && !resolvedExternalPackageDirs) {
            resolvedExternalPackageDirs = new Map();
            // We need to resolve all the external package dirs initially.
            for (const pkg of transpiledPackages){
                const pkgRes = await resolveExternal(dir, config.experimental.esmExternals, context, pkg + '/package.json', isEsmRequested, getResolve, isLocal ? resolveNextExternal : undefined);
                if (pkgRes.res) {
                    resolvedExternalPackageDirs.set(pkg, path.dirname(pkgRes.res));
                }
            }
        }
        const resolvedBundlingOptOutRes = resolveBundlingOptOutPackages({
            resolvedRes: res,
            config,
            resolvedExternalPackageDirs,
            isAppLayer,
            externalType,
            isOptOutBundling,
            request,
            transpiledPackages
        });
        if (resolvedBundlingOptOutRes) {
            return resolvedBundlingOptOutRes;
        }
        // if here, we default to bundling the file
        return;
    };
}
function resolveBundlingOptOutPackages({ resolvedRes, config, resolvedExternalPackageDirs, isAppLayer, externalType, isOptOutBundling, request, transpiledPackages }) {
    if (nodeModulesRegex.test(resolvedRes)) {
        const shouldBundlePages = !isAppLayer && config.bundlePagesRouterDependencies && !isOptOutBundling;
        const shouldBeBundled = shouldBundlePages || isResourceInPackages(resolvedRes, transpiledPackages, resolvedExternalPackageDirs);
        if (!shouldBeBundled) {
            return `${externalType} ${request}` // Externalize if not bundled or opted out
            ;
        }
    }
}
/**
 * @param localRes the full path to the file
 * @returns the externalized path
 * @description returns an externalized path if the file is a Next.js file and ends with either `.shared-runtime.js` or `.external.js`
 * This is used to ensure that files used across the rendering runtime(s) and the user code are one and the same. The logic in this function
 * will rewrite the require to the correct bundle location depending on the layer at which the file is being used.
 */ function resolveNextExternal(localRes) {
    const isExternal = externalPattern.test(localRes);
    // if the file ends with .external, we need to make it a commonjs require in all cases
    // this is used mainly to share the async local storage across the routing, rendering and user layers.
    if (isExternal) {
        // it's important we return the path that starts with `next/dist/` here instead of the absolute path
        // otherwise NFT will get tripped up
        return `commonjs ${normalizePathSep(localRes.replace(/.*?next[/\\]dist/, 'next/dist'))}`;
    }
}

//# sourceMappingURL=handle-externals.js.map