import { existsSync, readFileSync, readdirSync, realpathSync } from "node:fs";
import { createRequire } from "node:module";
import { dirname, isAbsolute, join, resolve } from "node:path";
const BUILTIN_SURFACE_PROFILES = {
    clr: {
        mode: "clr",
        extends: [],
        requiredTypeRoots: ["node_modules/@tsonic/globals"],
    },
};
const mergeUnique = (buckets) => {
    const merged = new Set();
    for (const bucket of buckets) {
        for (const value of bucket) {
            merged.add(value);
        }
    }
    return Array.from(merged);
};
const BUILTIN_SURFACE_MODE_SET = new Set(Object.keys(BUILTIN_SURFACE_PROFILES));
const isBuiltInSurfaceMode = (value) => BUILTIN_SURFACE_MODE_SET.has(value);
const normalizeSurfaceMode = (mode) => {
    if (typeof mode !== "string")
        return "clr";
    const trimmed = mode.trim();
    return trimmed.length > 0 ? trimmed : "clr";
};
const isSourceSurfacePackageRoot = (packageRoot) => {
    const manifestPath = join(packageRoot, "tsonic.package.json");
    if (!existsSync(manifestPath))
        return false;
    try {
        const parsed = JSON.parse(readFileSync(manifestPath, "utf-8"));
        return parsed.kind === "tsonic-source-package";
    }
    catch {
        return false;
    }
};
const readPackageName = (pkgJsonPath) => {
    if (!existsSync(pkgJsonPath))
        return undefined;
    try {
        const parsed = JSON.parse(readFileSync(pkgJsonPath, "utf-8"));
        return typeof parsed.name === "string" ? parsed.name : undefined;
    }
    catch {
        return undefined;
    }
};
const sortVersionDirs = (dirs) => {
    return [...dirs].sort((left, right) => {
        const leftNum = Number.parseInt(left, 10);
        const rightNum = Number.parseInt(right, 10);
        const leftIsNum = Number.isFinite(leftNum);
        const rightIsNum = Number.isFinite(rightNum);
        if (leftIsNum && rightIsNum)
            return rightNum - leftNum;
        if (leftIsNum)
            return -1;
        if (rightIsNum)
            return 1;
        return right.localeCompare(left);
    });
};
const resolveSiblingSearchRoots = (projectRoot) => {
    const roots = new Set();
    for (const candidateRoot of findAncestorLookupRoots(projectRoot)) {
        roots.add(resolve(candidateRoot));
        roots.add(resolve(candidateRoot, ".."));
    }
    return Array.from(roots);
};
const tryResolveSiblingSurfacePackage = (candidate, projectRoot) => {
    const scoped = candidate.match(/^@tsonic\/([^/]+)$/);
    if (!scoped)
        return undefined;
    const pkgDirName = scoped[1];
    if (!pkgDirName)
        return undefined;
    for (const searchRoot of resolveSiblingSearchRoots(projectRoot)) {
        const siblingRepoRoot = join(searchRoot, pkgDirName);
        const repoPackageName = readPackageName(join(siblingRepoRoot, "package.json"));
        if (repoPackageName === candidate) {
            return { packageName: candidate, packageRoot: siblingRepoRoot };
        }
        const versionsRoot = join(siblingRepoRoot, "versions");
        if (!existsSync(versionsRoot)) {
            continue;
        }
        const versionDirs = sortVersionDirs(readdirSync(versionsRoot, { withFileTypes: true })
            .filter((entry) => entry.isDirectory())
            .map((entry) => entry.name));
        for (const versionDir of versionDirs) {
            const candidateRoot = join(versionsRoot, versionDir);
            const name = readPackageName(join(candidateRoot, "package.json"));
            if (name === candidate) {
                return { packageName: candidate, packageRoot: candidateRoot };
            }
        }
    }
    return undefined;
};
const getSurfacePackageName = (mode) => {
    const trimmed = mode.trim();
    if (trimmed.length === 0 || trimmed === "clr")
        return undefined;
    return trimmed;
};
const findAncestorLookupRoots = (projectRoot) => {
    const roots = [];
    let current = resolve(projectRoot);
    let encounteredPackageJson = false;
    for (;;) {
        const hasPackageJson = existsSync(join(current, "package.json"));
        if (!encounteredPackageJson || hasPackageJson) {
            roots.push(current);
        }
        if (hasPackageJson) {
            encounteredPackageJson = true;
        }
        const parent = dirname(current);
        if (parent === current) {
            return roots;
        }
        current = parent;
    }
};
const tryResolveWorkspaceInstalledSurfacePackage = (packageName, projectRoot) => {
    const packagePathParts = packageName.startsWith("@")
        ? packageName.split("/")
        : [packageName];
    const candidateRoot = join(projectRoot, "node_modules", ...packagePathParts);
    if (!existsSync(join(candidateRoot, "package.json"))) {
        return undefined;
    }
    try {
        return {
            packageName,
            packageRoot: realpathSync(candidateRoot),
        };
    }
    catch {
        return {
            packageName,
            packageRoot: resolve(candidateRoot),
        };
    }
};
const tryResolveProjectInstalledSurfacePackage = (packageName, projectRoot) => {
    for (const candidateRoot of findAncestorLookupRoots(projectRoot)) {
        const installed = tryResolveWorkspaceInstalledSurfacePackage(packageName, candidateRoot);
        if (installed) {
            return installed;
        }
    }
    return undefined;
};
const resolveSurfacePackage = (mode, projectRoot, options) => {
    const req = createRequire(join(projectRoot, "package.json"));
    const packageName = getSurfacePackageName(mode);
    if (!packageName)
        return undefined;
    const authoritativePackageRoot = options.authoritativePackageRoots?.get(packageName);
    if (authoritativePackageRoot) {
        return {
            packageName,
            packageRoot: authoritativePackageRoot,
        };
    }
    const sibling = tryResolveSiblingSurfacePackage(packageName, projectRoot);
    if (sibling && isSourceSurfacePackageRoot(sibling.packageRoot)) {
        return sibling;
    }
    const workspaceInstalled = tryResolveProjectInstalledSurfacePackage(packageName, projectRoot);
    if (workspaceInstalled &&
        isSourceSurfacePackageRoot(workspaceInstalled.packageRoot)) {
        return workspaceInstalled;
    }
    if (workspaceInstalled &&
        existsSync(join(workspaceInstalled.packageRoot, "tsonic.surface.json"))) {
        return workspaceInstalled;
    }
    try {
        const pkgJsonPath = req.resolve(`${packageName}/package.json`);
        const installed = { packageName, packageRoot: dirname(pkgJsonPath) };
        if (sibling && isSourceSurfacePackageRoot(sibling.packageRoot)) {
            return sibling;
        }
        if (isSourceSurfacePackageRoot(installed.packageRoot)) {
            return installed;
        }
        if (sibling &&
            existsSync(join(sibling.packageRoot, "tsonic.surface.json"))) {
            return sibling;
        }
        if (existsSync(join(installed.packageRoot, "tsonic.surface.json"))) {
            return installed;
        }
        if (sibling &&
            existsSync(join(sibling.packageRoot, "tsonic.surface.json"))) {
            return sibling;
        }
        return installed;
    }
    catch {
        if (sibling)
            return sibling;
        return undefined;
    }
};
const parseManifestStringArray = (value) => {
    if (!Array.isArray(value))
        return undefined;
    const values = value
        .filter((entry) => typeof entry === "string")
        .map((entry) => entry.trim())
        .filter((entry) => entry.length > 0);
    return values.length > 0 ? values : [];
};
const normalizeExtendsMode = (mode, parentMode) => {
    const trimmedParent = parentMode.trim();
    if (trimmedParent.length === 0)
        return trimmedParent;
    if (trimmedParent === "clr" || trimmedParent.includes("/")) {
        return trimmedParent;
    }
    const scopedMatch = mode.match(/^(@[^/]+)\/[^/]+$/);
    if (scopedMatch?.[1]) {
        return `${scopedMatch[1]}/${trimmedParent}`;
    }
    return trimmedParent;
};
const resolveManifestTypeRoots = (packageRoot, requiredTypeRoots) => {
    const roots = requiredTypeRoots && requiredTypeRoots.length > 0
        ? requiredTypeRoots
        : ["."];
    return roots.map((entry) => isAbsolute(entry) ? entry : resolve(packageRoot, entry));
};
const loadCustomSurfaceProfile = (mode, options) => {
    const projectRoot = options.projectRoot;
    if (!projectRoot) {
        return undefined;
    }
    const resolvedPackage = resolveSurfacePackage(mode, projectRoot, options);
    if (!resolvedPackage)
        return undefined;
    const packageRoot = resolvedPackage.packageRoot;
    const manifestPath = join(packageRoot, "tsonic.surface.json");
    if (existsSync(manifestPath)) {
        let parsed;
        try {
            parsed = JSON.parse(readFileSync(manifestPath, "utf-8"));
        }
        catch {
            return undefined;
        }
        const extendsList = (parseManifestStringArray(parsed.extends) ?? []).map((parent) => normalizeExtendsMode(mode, parent));
        const requiredTypeRoots = resolveManifestTypeRoots(packageRoot, parseManifestStringArray(parsed.requiredTypeRoots));
        return {
            mode,
            extends: extendsList,
            requiredTypeRoots,
        };
    }
    if (isSourceSurfacePackageRoot(packageRoot)) {
        return {
            mode,
            extends: [],
            requiredTypeRoots: [packageRoot],
        };
    }
    return undefined;
};
const getSurfaceProfile = (mode, options) => {
    const projectRoot = options.projectRoot;
    if (projectRoot) {
        const custom = loadCustomSurfaceProfile(mode, options);
        if (custom)
            return custom;
    }
    if (isBuiltInSurfaceMode(mode)) {
        return BUILTIN_SURFACE_PROFILES[mode];
    }
    return {
        mode,
        extends: [],
        requiredTypeRoots: [],
    };
};
const resolveProfileChain = (mode, options) => {
    const seen = new Set();
    const chain = [];
    const visit = (currentMode) => {
        if (seen.has(currentMode))
            return;
        seen.add(currentMode);
        const profile = getSurfaceProfile(currentMode, options);
        for (const parent of profile.extends) {
            visit(parent);
        }
        chain.push(profile);
    };
    visit(mode);
    return chain;
};
export const resolveSurfaceCapabilities = (mode, options = {}) => {
    const normalizedMode = normalizeSurfaceMode(mode);
    const chain = resolveProfileChain(normalizedMode, options);
    return {
        mode: normalizedMode,
        includesClr: chain.some((profile) => profile.mode === "clr"),
        resolvedModes: chain.map((profile) => profile.mode),
        requiredTypeRoots: mergeUnique(chain.map((profile) => profile.requiredTypeRoots)),
    };
};
export const hasResolvedSurfaceProfile = (mode, options = {}) => {
    const normalizedMode = normalizeSurfaceMode(mode);
    if (BUILTIN_SURFACE_MODE_SET.has(normalizedMode))
        return true;
    if (!options.projectRoot)
        return false;
    return loadCustomSurfaceProfile(normalizedMode, options) !== undefined;
};
//# sourceMappingURL=profiles.js.map