import * as fs from "node:fs";
import * as path from "node:path";
const metadataCache = new Map();
const normalizePackageRoot = (packageRoot) => path.resolve(packageRoot);
const readPackageName = (packageRoot) => {
    const packageJsonPath = path.join(packageRoot, "package.json");
    if (!fs.existsSync(packageJsonPath)) {
        return undefined;
    }
    try {
        const parsed = JSON.parse(fs.readFileSync(packageJsonPath, "utf-8"));
        return typeof parsed.name === "string" && parsed.name.length > 0
            ? parsed.name
            : undefined;
    }
    catch {
        return undefined;
    }
};
const parseExports = (value) => {
    if (value === null || typeof value !== "object" || Array.isArray(value)) {
        return undefined;
    }
    const entries = {};
    for (const [key, target] of Object.entries(value)) {
        if (typeof key !== "string" ||
            key.length === 0 ||
            typeof target !== "string" ||
            target.length === 0) {
            return undefined;
        }
        entries[key] = target;
    }
    return entries;
};
const parseAmbient = (value) => {
    if (value === undefined) {
        return [];
    }
    if (!Array.isArray(value)) {
        return undefined;
    }
    const entries = [];
    for (const entry of value) {
        if (typeof entry !== "string" || entry.length === 0) {
            return undefined;
        }
        entries.push(entry);
    }
    return entries;
};
const normalizeModuleAliasTarget = (packageName, target) => {
    const trimmed = target.trim();
    if (trimmed.length === 0) {
        return undefined;
    }
    if (trimmed === ".") {
        return packageName;
    }
    if (trimmed.startsWith("./")) {
        return `${packageName}/${trimmed.slice(2)}`;
    }
    if (trimmed.startsWith("/")) {
        return undefined;
    }
    return trimmed;
};
const parseModuleAliases = (packageName, value) => {
    if (value === undefined) {
        return {};
    }
    if (value === null || typeof value !== "object" || Array.isArray(value)) {
        return undefined;
    }
    const entries = {};
    for (const [specifier, target] of Object.entries(value)) {
        if (typeof specifier !== "string" ||
            specifier.trim().length === 0 ||
            typeof target !== "string") {
            return undefined;
        }
        const resolvedTarget = normalizeModuleAliasTarget(packageName, target);
        if (!resolvedTarget) {
            return undefined;
        }
        entries[specifier] = resolvedTarget;
    }
    return entries;
};
const parseNamespace = (value) => {
    if (typeof value !== "string") {
        return undefined;
    }
    const trimmed = value.trim();
    return trimmed.length > 0 ? trimmed : undefined;
};
const resolveExportPaths = (packageRoot, exportsMap) => {
    const exportPaths = [];
    for (const target of Object.values(exportsMap)) {
        const absolute = path.resolve(packageRoot, target);
        if (!fs.existsSync(absolute)) {
            continue;
        }
        exportPaths.push(absolute);
    }
    return exportPaths;
};
const resolveAmbientPaths = (packageRoot, ambientEntries) => {
    const ambientPaths = [];
    for (const ambientEntry of ambientEntries) {
        const absolute = path.resolve(packageRoot, ambientEntry);
        if (!fs.existsSync(absolute)) {
            continue;
        }
        ambientPaths.push(absolute);
    }
    return ambientPaths;
};
const isPathWithinRoot = (filePath, rootPath) => {
    const relative = path.relative(path.resolve(rootPath), path.resolve(filePath));
    return (relative === "" ||
        (!relative.startsWith("..") && !path.isAbsolute(relative)));
};
const resolveCommonRootDir = (paths) => {
    const [first, ...remaining] = paths;
    if (!first) {
        throw new Error("resolveCommonRootDir requires at least one path");
    }
    let current = path.resolve(first);
    const rest = remaining.map((entry) => path.resolve(entry));
    for (;;) {
        if (rest.every((candidate) => isPathWithinRoot(candidate, current))) {
            return current;
        }
        const parent = path.dirname(current);
        if (parent === current) {
            return current;
        }
        current = parent;
    }
};
const resolveSourceRoot = (packageRoot, exportPaths) => {
    const srcRoot = path.join(packageRoot, "src");
    if (exportPaths.length === 0) {
        return fs.existsSync(srcRoot) ? srcRoot : packageRoot;
    }
    if (fs.existsSync(srcRoot) &&
        exportPaths.every((exportPath) => isPathWithinRoot(exportPath, srcRoot))) {
        return srcRoot;
    }
    return resolveCommonRootDir(exportPaths.map((exportPath) => path.dirname(exportPath)));
};
export const readSourcePackageMetadata = (packageRoot) => {
    const normalizedRoot = normalizePackageRoot(packageRoot);
    const cached = metadataCache.get(normalizedRoot);
    if (cached !== undefined) {
        return cached;
    }
    const manifestPath = path.join(normalizedRoot, "tsonic.package.json");
    if (!fs.existsSync(manifestPath)) {
        metadataCache.set(normalizedRoot, null);
        return null;
    }
    const packageName = readPackageName(normalizedRoot);
    if (!packageName) {
        metadataCache.set(normalizedRoot, null);
        return null;
    }
    try {
        const parsed = JSON.parse(fs.readFileSync(manifestPath, "utf-8"));
        if (parsed.kind !== "tsonic-source-package") {
            metadataCache.set(normalizedRoot, null);
            return null;
        }
        const source = parsed.source !== null &&
            typeof parsed.source === "object" &&
            !Array.isArray(parsed.source)
            ? parsed.source
            : undefined;
        if (!source) {
            metadataCache.set(normalizedRoot, null);
            return null;
        }
        const exportsMap = parseExports(source.exports);
        if (!exportsMap || Object.keys(exportsMap).length === 0) {
            metadataCache.set(normalizedRoot, null);
            return null;
        }
        const ambient = parseAmbient(source.ambient);
        if (ambient === undefined) {
            metadataCache.set(normalizedRoot, null);
            return null;
        }
        const moduleAliases = parseModuleAliases(packageName, source.moduleAliases);
        if (moduleAliases === undefined) {
            metadataCache.set(normalizedRoot, null);
            return null;
        }
        const namespace = parseNamespace(source.namespace);
        if (!namespace) {
            metadataCache.set(normalizedRoot, null);
            return null;
        }
        const exportPaths = resolveExportPaths(normalizedRoot, exportsMap);
        const metadata = {
            packageName,
            packageRoot: normalizedRoot,
            namespace,
            exports: exportsMap,
            exportPaths,
            ambient,
            ambientPaths: resolveAmbientPaths(normalizedRoot, ambient),
            moduleAliases,
            sourceRoot: resolveSourceRoot(normalizedRoot, exportPaths),
        };
        metadataCache.set(normalizedRoot, metadata);
        return metadata;
    }
    catch {
        metadataCache.set(normalizedRoot, null);
        return null;
    }
};
export const clearSourcePackageMetadataCachesForTests = () => {
    metadataCache.clear();
};
//# sourceMappingURL=source-package-metadata.js.map