// Utilities to support the inspection of node package "manifests"
import { dirname } from "path";
import { glob, readFile } from "./fs.js";
import { createGlobString, nicePath, normalizePath } from "./paths.js";
import { i18n } from "#utils";
/**
 * Helper for the TS type system to understand hasOwnProperty
 * and narrow a type appropriately.
 * @param obj the receiver of the hasOwnProperty method call
 * @param prop the property to test for
 */
function hasOwnProperty(obj, prop) {
    return Object.prototype.hasOwnProperty.call(obj, prop);
}
/**
 * Loads a package.json and validates that it is a JSON Object
 */
export function loadPackageManifest(logger, packageJsonPath) {
    const packageJson = JSON.parse(readFile(packageJsonPath));
    if (typeof packageJson !== "object" || !packageJson) {
        logger.error(i18n.file_0_not_an_object(nicePath(packageJsonPath)));
        return undefined;
    }
    return packageJson;
}
/**
 * Load the paths to packages specified in a Yarn workspace package JSON
 * Returns undefined if packageJSON does not define a Yarn workspace
 * @param packageJSON the package json object
 */
function getPackagePaths(packageJSON) {
    if (Array.isArray(packageJSON["workspaces"]) &&
        packageJSON["workspaces"].every((i) => typeof i === "string")) {
        return packageJSON["workspaces"];
    }
    if (typeof packageJSON["workspaces"] === "object" &&
        packageJSON["workspaces"] != null) {
        const workspaces = packageJSON["workspaces"];
        if (hasOwnProperty(workspaces, "packages") &&
            Array.isArray(workspaces["packages"]) &&
            workspaces["packages"].every((i) => typeof i === "string")) {
            return workspaces["packages"];
        }
    }
    return undefined;
}
/**
 * Given a list of (potentially wildcard containing) package paths,
 * return all the actual package folders found.
 */
export function expandPackages(logger, packageJsonDir, workspaces, exclude) {
    // Technically npm and Yarn workspaces don't support recursive nesting,
    // however we support the passing of paths to either packages or
    // to the root of a workspace tree in our params and so we could here
    // be dealing with either a root or a leaf. So let's do this recursively,
    // as it actually is simpler from an implementation perspective anyway.
    return workspaces.flatMap((workspace) => {
        const expandedPackageJsonPaths = glob(createGlobString(packageJsonDir, `${workspace}/package.json`), packageJsonDir);
        if (expandedPackageJsonPaths.length === 0) {
            logger.warn(i18n.entry_point_0_did_not_match_any_packages(nicePath(workspace)));
        }
        else if (expandedPackageJsonPaths.length !== 1) {
            logger.verbose(`Expanded ${nicePath(workspace)} to:\n\t${expandedPackageJsonPaths
                .map(nicePath)
                .join("\n\t")}`);
        }
        return expandedPackageJsonPaths.flatMap((packageJsonPath) => {
            if (exclude.matchesAny(dirname(packageJsonPath))) {
                return [];
            }
            const packageJson = loadPackageManifest(logger, packageJsonPath);
            if (packageJson === undefined) {
                return [];
            }
            const packagePaths = getPackagePaths(packageJson);
            if (packagePaths === undefined) {
                // Assume this is a single package repo
                return [dirname(packageJsonPath)];
            }
            // This is a workspace root package, recurse
            return expandPackages(logger, normalizePath(dirname(packageJsonPath)), packagePaths.map(p => createGlobString(normalizePath(dirname(packageJsonPath)), p)), exclude);
        });
    });
}
