import path from 'node:path';
import { readFileSync, existsSync } from 'node:fs';
import { pathToFileURL } from 'node:url';
import * as CommentJson from 'next/dist/compiled/comment-json';
import { deregisterHook, registerHook, requireFromString } from './require-hook';
import { warn, warnOnce } from '../output/log';
import { getNodeOptionsArgs } from '../../server/lib/utils';
function resolveSWCOptions(cwd, compilerOptions) {
    var _process_versions, _process;
    return {
        jsc: {
            parser: {
                syntax: 'typescript'
            },
            ...compilerOptions.paths ? {
                paths: compilerOptions.paths
            } : {},
            ...compilerOptions.baseUrl ? {
                baseUrl: path.resolve(cwd, compilerOptions.baseUrl)
            } : compilerOptions.paths ? {
                baseUrl: cwd
            } : {}
        },
        module: {
            type: 'commonjs'
        },
        isModule: 'unknown',
        env: {
            targets: {
                // Setting the Node.js version can reduce unnecessary code generation.
                node: ((_process = process) == null ? void 0 : (_process_versions = _process.versions) == null ? void 0 : _process_versions.node) ?? '20.19.0'
            }
        }
    };
}
function resolveExtends(extendsPath, currentConfigDir) {
    // Relative paths are resolved relative to the current config's directory
    if (extendsPath.startsWith('./') || extendsPath.startsWith('../') || path.isAbsolute(extendsPath)) {
        const resolved = path.resolve(currentConfigDir, extendsPath);
        // TypeScript allows omitting .json extension
        if (existsSync(resolved)) {
            return resolved;
        }
        if (!resolved.endsWith('.json') && existsSync(resolved + '.json')) {
            return resolved + '.json';
        }
        return resolved;
    }
    // Package paths - use require.resolve to find the package
    try {
        // Try resolving as a direct path within the package
        return require.resolve(extendsPath, {
            paths: [
                currentConfigDir
            ]
        });
    } catch  {
        // If that fails, try appending tsconfig.json for package names like "@tsconfig/node18"
        try {
            return require.resolve(extendsPath + '/tsconfig.json', {
                paths: [
                    currentConfigDir
                ]
            });
        } catch  {
            // Return the original path and let it fail later with a clear error
            return path.resolve(currentConfigDir, extendsPath);
        }
    }
}
function loadTsConfigFile(configPath, visited) {
    const resolvedPath = path.resolve(configPath);
    if (visited.has(resolvedPath)) {
        return {};
    }
    visited.add(resolvedPath);
    if (!existsSync(resolvedPath)) {
        return {};
    }
    const configContent = readFileSync(resolvedPath, 'utf8');
    const config = CommentJson.parse(configContent);
    const configDir = path.dirname(resolvedPath);
    let mergedOptions = {};
    // Note that config options from `extends` should get overwritten, not merged
    if (config.extends) {
        const extendsList = Array.isArray(config.extends) ? config.extends : [
            config.extends
        ];
        for (const extendsPath of extendsList){
            const parentConfigPath = resolveExtends(extendsPath, configDir);
            const parentOptions = loadTsConfigFile(parentConfigPath, visited);
            mergedOptions = {
                ...mergedOptions,
                ...parentOptions
            };
        }
    }
    const currentOptions = config.compilerOptions ?? {};
    mergedOptions = {
        ...mergedOptions,
        paths: currentOptions.paths ?? mergedOptions.paths,
        baseUrl: currentOptions.baseUrl ?? mergedOptions.baseUrl
    };
    return mergedOptions;
}
async function loadTsConfig(dir) {
    // NOTE: This doesn't fully cover the edge case for setting
    // "typescript.tsconfigPath" in next config which is currently
    // a restriction.
    // It's a chicken-and-egg problem since we need to transpile
    // the next config to get that value.
    const resolvedTsConfigPath = path.join(dir, 'tsconfig.json');
    if (!existsSync(resolvedTsConfigPath)) {
        return {};
    }
    return loadTsConfigFile(resolvedTsConfigPath, new Set());
}
export async function transpileConfig({ nextConfigPath, dir }) {
    try {
        // envs are passed to the workers and preserve the flag
        if (process.env.__NEXT_NODE_NATIVE_TS_LOADER_ENABLED === 'true') {
            try {
                // Node.js v22.10.0+
                // Value is 'strip' or 'transform' based on how the feature is enabled.
                // https://nodejs.org/api/process.html#processfeaturestypescript
                // TODO: Remove `as any` once we bump @types/node to v22.10.0+
                if (process.features.typescript) {
                    // Run import() here to catch errors and fallback to legacy resolution.
                    return (await import(pathToFileURL(nextConfigPath).href)).default;
                }
                if (getNodeOptionsArgs().includes('--no-experimental-strip-types') || process.execArgv.includes('--no-experimental-strip-types')) {
                    warnOnce(`Skipped resolving "${path.basename(nextConfigPath)}" using Node.js native TypeScript resolution because it was disabled by the "--no-experimental-strip-types" flag.` + ' Falling back to legacy resolution.' + ' Learn more: https://nextjs.org/docs/app/api-reference/config/typescript#using-nodejs-native-typescript-resolver-for-nextconfigts');
                }
                // Feature is not enabled, fallback to legacy resolution for current session.
                process.env.__NEXT_NODE_NATIVE_TS_LOADER_ENABLED = 'false';
            } catch (cause) {
                warnOnce(`Failed to import "${path.basename(nextConfigPath)}" using Node.js native TypeScript resolution.` + ' Falling back to legacy resolution.' + ' Learn more: https://nextjs.org/docs/app/api-reference/config/typescript#using-nodejs-native-typescript-resolver-for-nextconfigts', {
                    cause
                });
                // Once failed, fallback to legacy resolution for current session.
                process.env.__NEXT_NODE_NATIVE_TS_LOADER_ENABLED = 'false';
            }
        }
        const compilerOptions = await loadTsConfig(dir);
        return handleCJS({
            dir,
            nextConfigPath,
            compilerOptions
        });
    } catch (cause) {
        throw Object.defineProperty(new Error(`Failed to transpile "${path.basename(nextConfigPath)}".`, {
            cause
        }), "__NEXT_ERROR_CODE", {
            value: "E797",
            enumerable: false,
            configurable: true
        });
    }
}
async function handleCJS({ dir, nextConfigPath, compilerOptions }) {
    const swcOptions = resolveSWCOptions(dir, compilerOptions);
    let hasRequire = false;
    try {
        var _config_experimental;
        const nextConfigString = readFileSync(nextConfigPath, 'utf8');
        // lazy require swc since it loads React before even setting NODE_ENV
        // resulting loading Development React on Production
        const { loadBindings } = require('../swc');
        const bindings = await loadBindings();
        const { code } = await bindings.transform(nextConfigString, swcOptions);
        // register require hook only if require exists
        if (code.includes('require(')) {
            registerHook(swcOptions);
            hasRequire = true;
        }
        // filename & extension don't matter here
        const config = requireFromString(code, path.resolve(dir, 'next.config.compiled.js'));
        // At this point we have already loaded the bindings without this configuration setting due to the `transform` call above.
        // Possibly we fell back to wasm in which case, it all works out but if not we need to warn
        // that the configuration was ignored.
        if ((config == null ? void 0 : (_config_experimental = config.experimental) == null ? void 0 : _config_experimental.useWasmBinary) && !bindings.isWasm) {
            warn('Using a next.config.ts file is incompatible with `experimental.useWasmBinary` unless ' + '`--experimental-next-config-strip-types` is also passed.\nSetting `useWasmBinary` to `false');
            config.experimental.useWasmBinary = false;
        }
        return config;
    } catch (error) {
        throw error;
    } finally{
        if (hasRequire) {
            deregisterHook();
        }
    }
}

//# sourceMappingURL=transpile-config.js.map