/**
 * Module resolution helpers for Tsonic program creation.
 *
 * Provides package-root resolution, @tsonic/* module resolution,
 * and namespace remapping logic.
 */
import * as ts from "typescript";
import * as path from "node:path";
import * as fs from "node:fs";
import { readSourcePackageMetadata } from "./source-package-metadata.js";
/**
 * Read the `name` field from a package.json file.
 */
export const readPackageName = (pkgJsonPath) => {
    if (!fs.existsSync(pkgJsonPath))
        return undefined;
    try {
        const parsed = JSON.parse(fs.readFileSync(pkgJsonPath, "utf-8"));
        return typeof parsed.name === "string" ? parsed.name : undefined;
    }
    catch {
        return undefined;
    }
};
/**
 * Read the root namespace from a package's native source-package metadata or
 * CLR bindings payload.
 */
export const createReadPackageRootNamespace = (packageRootNamespaceCache) => {
    const readPackageRootNamespace = (packageRoot) => {
        const cached = packageRootNamespaceCache.get(packageRoot);
        if (cached !== undefined) {
            return cached ?? undefined;
        }
        const sourceMetadata = readSourcePackageMetadata(packageRoot);
        if (sourceMetadata?.namespace) {
            packageRootNamespaceCache.set(packageRoot, sourceMetadata.namespace);
            return sourceMetadata.namespace;
        }
        const candidates = [
            path.join(packageRoot, "index", "bindings.json"),
            path.join(packageRoot, "bindings.json"),
        ];
        for (const candidate of candidates) {
            if (!fs.existsSync(candidate))
                continue;
            try {
                const parsed = JSON.parse(fs.readFileSync(candidate, "utf-8"));
                if (typeof parsed.namespace === "string" &&
                    parsed.namespace.length > 0) {
                    packageRootNamespaceCache.set(packageRoot, parsed.namespace);
                    return parsed.namespace;
                }
            }
            catch {
                // Ignore malformed/non-namespace CLR bindings candidates and continue.
            }
        }
        packageRootNamespaceCache.set(packageRoot, null);
        return undefined;
    };
    return readPackageRootNamespace;
};
/**
 * Parse a `@tsonic/*` module request into its components.
 */
export const parseTsonicModuleRequest = (moduleName) => {
    const match = moduleName.match(/^@tsonic\/([^/]+)(?:\/(.+))?$/);
    if (!match)
        return undefined;
    const pkgDirName = match[1];
    if (!pkgDirName)
        return undefined;
    return {
        packageName: `@tsonic/${pkgDirName}`,
        pkgDirName,
        subpath: match[2],
    };
};
/**
 * Resolve a module from a package root by trying candidate file paths.
 */
export const createResolveModuleFromPackageRoot = (packageRootModuleResolutionCache, readPackageRootNamespace) => {
    const tryResolveExportTarget = (packageRoot, target) => {
        if (typeof target !== "string" || target.length === 0) {
            return undefined;
        }
        const candidate = path.resolve(packageRoot, target);
        if (!fs.existsSync(candidate)) {
            return undefined;
        }
        const extension = candidate.endsWith(".d.ts")
            ? ts.Extension.Dts
            : candidate.endsWith(".ts")
                ? ts.Extension.Ts
                : candidate.endsWith(".js")
                    ? ts.Extension.Js
                    : candidate.endsWith(".mts")
                        ? ts.Extension.Mts
                        : candidate.endsWith(".cts")
                            ? ts.Extension.Cts
                            : undefined;
        if (!extension) {
            return undefined;
        }
        return {
            resolvedFileName: candidate,
            extension,
            isExternalLibraryImport: true,
        };
    };
    const tryResolveFromPackageJsonExports = (packageRoot, subpath) => {
        const packageJsonPath = path.join(packageRoot, "package.json");
        if (!fs.existsSync(packageJsonPath)) {
            return undefined;
        }
        try {
            const parsed = JSON.parse(fs.readFileSync(packageJsonPath, "utf-8"));
            const exportsField = parsed.exports;
            if (exportsField === null ||
                typeof exportsField !== "object" ||
                Array.isArray(exportsField)) {
                return undefined;
            }
            const exportKey = subpath && subpath.length > 0 ? `./${subpath}` : ".";
            const rawTarget = exportsField[exportKey];
            if (rawTarget === undefined) {
                return undefined;
            }
            if (typeof rawTarget === "string") {
                return tryResolveExportTarget(packageRoot, rawTarget);
            }
            if (rawTarget !== null &&
                typeof rawTarget === "object" &&
                !Array.isArray(rawTarget)) {
                const resolvedFromTypes = tryResolveExportTarget(packageRoot, rawTarget.types);
                if (resolvedFromTypes) {
                    return resolvedFromTypes;
                }
                return tryResolveExportTarget(packageRoot, rawTarget.default);
            }
        }
        catch {
            return undefined;
        }
        return undefined;
    };
    const tryResolveFromSourceManifest = (packageRoot, subpath) => {
        const manifestPath = path.join(packageRoot, "tsonic.package.json");
        if (!fs.existsSync(manifestPath)) {
            return undefined;
        }
        try {
            const parsed = JSON.parse(fs.readFileSync(manifestPath, "utf-8"));
            const exportsField = parsed.source?.exports;
            if (exportsField === null ||
                typeof exportsField !== "object" ||
                Array.isArray(exportsField)) {
                return undefined;
            }
            const exportKey = subpath && subpath.length > 0 ? `./${subpath}` : ".";
            return tryResolveExportTarget(packageRoot, exportsField[exportKey]);
        }
        catch {
            return undefined;
        }
    };
    const resolveModuleFromPackageRoot = (packageRoot, subpath) => {
        const cacheKey = `${path.resolve(packageRoot)}::${subpath ?? "."}`;
        const cached = packageRootModuleResolutionCache.get(cacheKey);
        if (cached !== undefined) {
            return cached ?? undefined;
        }
        const sourcePackageMetadata = readSourcePackageMetadata(packageRoot);
        const exportedResolution = sourcePackageMetadata
            ? (tryResolveFromSourceManifest(packageRoot, subpath) ??
                tryResolveFromPackageJsonExports(packageRoot, subpath))
            : (tryResolveFromPackageJsonExports(packageRoot, subpath) ??
                tryResolveFromSourceManifest(packageRoot, subpath));
        if (exportedResolution) {
            packageRootModuleResolutionCache.set(cacheKey, exportedResolution);
            return exportedResolution;
        }
        if (sourcePackageMetadata) {
            packageRootModuleResolutionCache.set(cacheKey, null);
            return undefined;
        }
        const buildCandidates = (candidateSubpath) => {
            if (!candidateSubpath || candidateSubpath.length === 0) {
                return [
                    path.join(packageRoot, "index.d.ts"),
                    path.join(packageRoot, "index.js"),
                ];
            }
            const basePath = path.join(packageRoot, candidateSubpath);
            if (candidateSubpath.endsWith(".d.ts")) {
                return [basePath];
            }
            if (candidateSubpath.endsWith(".js")) {
                return [basePath.replace(/\.js$/, ".d.ts"), basePath];
            }
            return [
                `${basePath}.d.ts`,
                `${basePath}.js`,
                path.join(basePath, "index.d.ts"),
                path.join(basePath, "index.js"),
            ];
        };
        const remappedRootNamespaceSubpath = (() => {
            if (!subpath || subpath.length === 0)
                return undefined;
            const rootNamespace = readPackageRootNamespace(packageRoot);
            if (!rootNamespace)
                return undefined;
            if (subpath === `${rootNamespace}.js` ||
                subpath === `${rootNamespace}.d.ts`) {
                return "index.js";
            }
            if (subpath.startsWith(`${rootNamespace}/`)) {
                return `index/${subpath.slice(rootNamespace.length + 1)}`;
            }
            return undefined;
        })();
        const candidates = [
            ...buildCandidates(subpath),
            ...buildCandidates(remappedRootNamespaceSubpath),
        ];
        const seenCandidates = new Set();
        for (const candidate of candidates) {
            if (seenCandidates.has(candidate))
                continue;
            seenCandidates.add(candidate);
            if (!fs.existsSync(candidate))
                continue;
            const extension = candidate.endsWith(".d.ts")
                ? ts.Extension.Dts
                : candidate.endsWith(".js")
                    ? ts.Extension.Js
                    : candidate.endsWith(".ts")
                        ? ts.Extension.Ts
                        : undefined;
            if (!extension)
                continue;
            const resolvedModule = {
                resolvedFileName: candidate,
                extension,
                isExternalLibraryImport: true,
            };
            packageRootModuleResolutionCache.set(cacheKey, resolvedModule);
            return resolvedModule;
        }
        packageRootModuleResolutionCache.set(cacheKey, null);
        return undefined;
    };
    return resolveModuleFromPackageRoot;
};
//# sourceMappingURL=module-resolution.js.map