/**
 * Live-status probe for the console Stacks section.
 *
 * Mirrors doctor's detection chain exactly so the console and `lisa doctor`
 * can never disagree. An empty match is the honest empty value (`[]`), never
 * unknown; the runner maps a thrown detector error to unknown.
 * @module cli/ui-detected-stacks
 */
import { realpath } from "node:fs/promises";
import { createDetectorRegistry, } from "../detection/index.js";
import { projectPathKind, readProjectText } from "../health/read-only-fs.js";
import { isJsonObject } from "../sync/json-path.js";
/** Probe id for the detected-stacks live-status entry. */
export const DETECTED_STACKS_PROBE_ID = "detected-stacks";
const PACKAGE_JSON_RELATIVE = "package.json";
const HARPER_CONFIG_RELATIVE = "harper-app/config.yaml";
const MARKER_NAMES = [
    "tsconfig.json",
    "app.json",
    "eas.json",
    "nest-cli.json",
    "cdk.json",
    HARPER_CONFIG_RELATIVE,
    "harper-app/schema.graphql",
    "bin/rails",
    "config/application.rb",
];
/**
 * Return whether a confined regular marker file exists.
 * @param root - Canonical project root
 * @param relativePath - Project-relative marker path
 * @returns Whether the marker is a regular no-follow file
 */
async function confinedMarkerExists(root, relativePath) {
    return (await projectPathKind(root, relativePath)) === "file";
}
/**
 * Return package dependency names from a validated object.
 * @param packageJson - Parsed package manifest
 * @returns Production and development dependency names
 */
function dependencyNames(packageJson) {
    if (!isJsonObject(packageJson))
        return [];
    return [packageJson.dependencies, packageJson.devDependencies].flatMap(dependencies => isJsonObject(dependencies) ? Object.keys(dependencies) : []);
}
/**
 * Return whether a parsed package manifest describes a publishable package.
 * @param packageJson - Parsed package manifest
 * @returns Whether the npm-package detector signal is present
 */
function isPublishablePackage(packageJson) {
    return (isJsonObject(packageJson) &&
        packageJson.private !== true &&
        (packageJson.main !== undefined ||
            packageJson.bin !== undefined ||
            packageJson.exports !== undefined ||
            (Array.isArray(packageJson.files) && packageJson.files.length > 0)));
}
/**
 * Return detector results from confined filesystem signals.
 * @param signals - Bounded package, marker, and Harper evidence
 * @returns Direct detector matches before parent expansion
 */
function classifyDetectedTypes(signals) {
    const hasMarker = (name) => signals.markers.has(name);
    const hasDependency = (name) => signals.dependencies.includes(name);
    const hasDependencyPrefix = (prefix) => signals.dependencies.some(name => name.startsWith(prefix));
    const harperConfigMatches = signals.harperConfig?.includes("graphqlSchema:") === true &&
        signals.harperConfig.includes("jsResource:") &&
        signals.harperConfig.includes("static:");
    return [
        hasMarker("tsconfig.json") || hasDependency("typescript")
            ? ["typescript"]
            : [],
        isPublishablePackage(signals.packageJson) ? ["npm-package"] : [],
        hasMarker(HARPER_CONFIG_RELATIVE) &&
            hasMarker("harper-app/schema.graphql") &&
            (harperConfigMatches || hasDependency("harperdb"))
            ? ["harper-fabric"]
            : [],
        hasDependency("phaser") ? ["phaser"] : [],
        hasMarker("app.json") || hasMarker("eas.json") || hasDependency("expo")
            ? ["expo"]
            : [],
        hasMarker("nest-cli.json") || hasDependencyPrefix("@nestjs")
            ? ["nestjs"]
            : [],
        hasMarker("cdk.json") || hasDependencyPrefix("aws-cdk")
            ? ["cdk"]
            : [],
        hasMarker("bin/rails") || hasMarker("config/application.rb")
            ? ["rails"]
            : [],
    ].flat();
}
/**
 * Detect setup-automation stacks using only bounded, no-follow project reads.
 * This mirrors the authoritative detector signals without invoking their
 * legacy unbounded readers on the setup-readiness request path.
 * @param destDir - Project root
 * @returns Expanded types in canonical detector-registry order
 */
export async function readConfinedDetectedStacks(destDir) {
    const root = await realpath(destDir);
    const packageText = await readProjectText(root, PACKAGE_JSON_RELATIVE);
    const packageJson = packageText === undefined
        ? undefined
        : JSON.parse(packageText);
    const markerValues = await Promise.all(MARKER_NAMES.map(name => confinedMarkerExists(root, name)));
    const markers = new Set(MARKER_NAMES.filter((_name, index) => markerValues[index]));
    const harperConfig = markers.has(HARPER_CONFIG_RELATIVE)
        ? await readProjectText(root, HARPER_CONFIG_RELATIVE)
        : undefined;
    const detected = classifyDetectedTypes({
        packageJson,
        dependencies: dependencyNames(packageJson),
        markers,
        harperConfig,
    });
    return createDetectorRegistry().expandAndOrderTypes([...detected]);
}
/**
 * Create the probe that reports the project's detected stacks.
 * @param destDir - Project root scanned by the detector registry
 * @param registry - Injectable detector registry used by focused tests
 * @returns Probe emitting the expanded, ordered project types
 */
export function createDetectedStacksProbe(destDir, registry = createDetectorRegistry()) {
    return {
        id: DETECTED_STACKS_PROBE_ID,
        timeoutMs: 5_000,
        run: async () => {
            const types = registry.expandAndOrderTypes(await registry.detectAll(destDir));
            return { state: "value", value: [...types] };
        },
    };
}
//# sourceMappingURL=ui-detected-stacks.js.map