/**
 * CLR Bindings Resolve Logic
 *
 * Core resolution logic for CLR namespace imports: specifier parsing,
 * namespace key extraction, facade path resolution, and bindings.json
 * discovery.
 */
import { dirname, join } from "node:path";
import { createRequire } from "node:module";
import { resolvePkgRoot, hasBindings, extractNamespace, extractAssembly, } from "./clr-bindings-package-resolution.js";
/**
 * Resolver for CLR namespace imports
 *
 * An import is a CLR namespace import iff:
 * - It's a package import with a subpath (e.g., @scope/pkg/Namespace)
 * - The package contains bindings.json in that subpath directory
 *
 * This works for any bindings provider package.
 */
export class ClrBindingsResolver {
    constructor(baseDir) {
        // Cache: packageName -> packageRoot (or null if not found)
        this.pkgRootCache = new Map();
        // Cache: absolute bindingsPath -> exists
        this.bindingsExistsCache = new Map();
        // Cache: absolute bindingsPath -> namespace (extracted from bindings.json)
        this.namespaceCache = new Map();
        // Cache: absolute bindingsPath -> assembly (extracted from bindings.json types)
        this.assemblyCache = new Map();
        // Cache: module specifier -> resolved absolute path (or null if not found)
        this.resolvedPathCache = new Map();
        // Create a require function that resolves relative to baseDir
        this.require = createRequire(join(baseDir, "package.json"));
    }
    /**
     * Resolve an import specifier to determine if it's a CLR namespace import
     */
    resolve(moduleSpecifier) {
        // Skip local imports
        if (moduleSpecifier.startsWith(".") || moduleSpecifier.startsWith("/")) {
            return { isClr: false };
        }
        // Parse the specifier into package name and subpath
        const parsed = this.parseModuleSpecifier(moduleSpecifier);
        if (!parsed) {
            return { isClr: false };
        }
        const { packageName, subpath } = parsed;
        // No subpath means it's not a namespace import (e.g., just "@scope/pkg")
        if (!subpath) {
            return { isClr: false };
        }
        // Extract the namespace key from the import subpath.
        const namespaceKey = this.extractNamespaceKey(subpath);
        if (namespaceKey === null)
            return { isClr: false };
        // Prefer exports-aware resolution
        const facadeSpecifier = namespaceKey === ""
            ? `${packageName}/index.js`
            : `${packageName}/${namespaceKey}.js`;
        const resolvedFacadePath = this.resolveModulePath(packageName, facadeSpecifier);
        // Resolve package root using Node resolution (for bounding searches).
        const pkgRoot = resolvePkgRoot(packageName, this.pkgRootCache, this.require);
        if (!pkgRoot) {
            return { isClr: false };
        }
        const bindingsPath = resolvedFacadePath &&
            this.findBindingsPathFromFacade({
                pkgRoot,
                namespaceKey,
                resolvedFacadePath,
            });
        if (!bindingsPath) {
            return { isClr: false };
        }
        // Extract namespace from bindings.json (tsbindgen format)
        const resolvedNamespace = extractNamespace(bindingsPath, this.namespaceCache);
        if (!resolvedNamespace) {
            return { isClr: false };
        }
        // Extract assembly name from bindings.json types
        const assembly = extractAssembly(bindingsPath, this.assemblyCache);
        return {
            isClr: true,
            packageName,
            resolvedNamespace,
            bindingsPath,
            assembly,
        };
    }
    extractNamespaceKey(subpath) {
        const slashIdx = subpath.indexOf("/");
        const backslashIdx = subpath.indexOf("\\");
        const firstSep = slashIdx === -1
            ? backslashIdx
            : backslashIdx === -1
                ? slashIdx
                : Math.min(slashIdx, backslashIdx);
        const firstSeg = (firstSep === -1 ? subpath : subpath.slice(0, firstSep)).trim();
        if (!firstSeg)
            return null;
        const normalized = firstSeg.endsWith(".js")
            ? firstSeg.slice(0, -3)
            : firstSeg;
        return normalized === "index" ? "" : normalized;
    }
    resolveModulePath(_packageName, specifier) {
        const cached = this.resolvedPathCache.get(specifier);
        if (cached !== undefined)
            return cached;
        const resolveViaRequire = (req) => {
            try {
                return req.resolve(specifier);
            }
            catch {
                return null;
            }
        };
        const direct = resolveViaRequire(this.require);
        if (direct) {
            this.resolvedPathCache.set(specifier, direct);
            return direct;
        }
        this.resolvedPathCache.set(specifier, null);
        return null;
    }
    findBindingsPathFromFacade(opts) {
        const { pkgRoot, namespaceKey, resolvedFacadePath } = opts;
        let currentDir = dirname(resolvedFacadePath);
        while (true) {
            const siblingNamespace = namespaceKey === "" ? "index" : namespaceKey;
            const sibling = join(currentDir, siblingNamespace, "bindings.json");
            if (namespaceKey === ""
                ? hasBindings(sibling, this.bindingsExistsCache)
                : this.hasBindingsForNamespace(sibling, namespaceKey)) {
                return sibling;
            }
            const direct = join(currentDir, "bindings.json");
            if (namespaceKey === ""
                ? hasBindings(direct, this.bindingsExistsCache)
                : this.hasBindingsForNamespace(direct, namespaceKey)) {
                return direct;
            }
            if (currentDir === pkgRoot)
                return null;
            const parent = dirname(currentDir);
            if (parent === currentDir)
                return null;
            currentDir = parent;
        }
    }
    parseModuleSpecifier(spec) {
        if (spec.startsWith("@")) {
            const match = spec.match(/^(@[^/]+\/[^/]+)(?:\/(.+))?$/);
            if (!match)
                return null;
            const packageName = match[1];
            const subpath = match[2];
            if (!packageName || !subpath)
                return null;
            return { packageName, subpath };
        }
        else {
            const slashIdx = spec.indexOf("/");
            if (slashIdx === -1)
                return null;
            return {
                packageName: spec.slice(0, slashIdx),
                subpath: spec.slice(slashIdx + 1),
            };
        }
    }
    hasBindingsForNamespace(bindingsPath, expectedNamespace) {
        if (!hasBindings(bindingsPath, this.bindingsExistsCache))
            return false;
        const resolvedNamespace = extractNamespace(bindingsPath, this.namespaceCache);
        return resolvedNamespace === expectedNamespace;
    }
    /**
     * Get all discovered binding paths (for loading)
     */
    getDiscoveredBindingPaths() {
        const paths = new Set();
        for (const [path, exists] of this.bindingsExistsCache) {
            if (exists) {
                paths.add(path);
            }
        }
        return paths;
    }
}
//# sourceMappingURL=clr-bindings-resolve-logic.js.map