// @crash-shield-ignore-file — config/cache reader for Planu-controlled JSON; writer is this codebase, shape guaranteed by build/seed.
// engine/plugin-installer/stack-matcher.ts — Registry-driven stack signal normalization (SPEC-596 + SPEC-597 + SPEC-605)
//
// Reads src/config/framework-registry/index.json and src/config/detection-signals.json
// at first use (synchronously, once per process), builds an alias → canonical-name map
// dynamically, and normalizes raw stack strings from scan_project / ProjectKnowledge / user hints.
//
// ZERO HARDCODE: adding a new framework or deploy target = edit the JSON registries,
// never touch this file.
import { readFileSync } from 'node:fs';
import { readFile } from 'node:fs/promises';
import { fileURLToPath } from 'node:url';
import { dirname, join, resolve as pathResolve } from 'node:path';
let aliasMapCache = null;
function registryDir() {
    const here = fileURLToPath(import.meta.url);
    return pathResolve(dirname(here), '..', '..', 'config');
}
function addAliasTo(entries, seen, alias, canonical) {
    const key = alias.toLowerCase().trim();
    if (key.length === 0 || seen.has(key)) {
        return;
    }
    seen.add(key);
    entries.push([key, canonical]);
}
/**
 * Build the alias → canonical map from the registries (synchronous, cached).
 * Canonical names are the framework `id` (for frameworks) or deploy-target / database `id`.
 *
 * Aliases added automatically:
 * - Framework: id, displayName lower, displayName without dots/spaces
 * - Deploy target: id
 * - Database: id
 * - Dependency signal patterns: pattern → frameworkId (when simple)
 */
function buildAliasMap() {
    if (aliasMapCache) {
        return aliasMapCache;
    }
    const fwRaw = readFileSync(join(registryDir(), 'framework-registry', 'index.json'), 'utf-8');
    const fw = JSON.parse(fwRaw);
    const sigRaw = readFileSync(join(registryDir(), 'detection-signals.json'), 'utf-8');
    const signals = JSON.parse(sigRaw);
    const entries = [];
    const seen = new Set();
    for (const f of fw.frameworks) {
        addAliasTo(entries, seen, f.id, f.id);
        addAliasTo(entries, seen, f.displayName, f.id);
        addAliasTo(entries, seen, f.displayName.replace(/[.\s]/g, ''), f.id);
    }
    for (const target of signals.deployTargets) {
        addAliasTo(entries, seen, target.id, target.id);
    }
    for (const db of signals.databases) {
        addAliasTo(entries, seen, db.id, db.id);
    }
    for (const sig of signals.signals) {
        if (sig.type !== 'dependency') {
            continue;
        }
        if (sig.pattern.includes('/') || sig.pattern.includes('*')) {
            continue;
        }
        addAliasTo(entries, seen, sig.pattern, sig.frameworkId);
    }
    // Explicit aliases from the registry (e.g. "otel" → "opentelemetry", "shadcn/ui" → "shadcn").
    for (const entry of signals.aliases ?? []) {
        addAliasTo(entries, seen, entry.alias, entry.canonical);
    }
    aliasMapCache = Object.fromEntries(entries);
    return aliasMapCache;
}
/** Test helper — clears the alias-map cache. Production never calls this. */
export function resetStackMatcherCache() {
    aliasMapCache = null;
}
function normalizeSignal(raw) {
    const map = buildAliasMap();
    const lower = raw.toLowerCase().trim();
    return map[lower] ?? lower;
}
/**
 * SPEC-605: Read package.json from projectPath, extract all dependency names,
 * and pass them through matchStackSignals() to produce StackSignal[].
 * Falls back to empty array when package.json is absent or unreadable.
 */
export async function scanProjectForStack(projectPath) {
    try {
        const pkgRaw = await readFile(join(projectPath, 'package.json'), 'utf-8');
        const pkg = JSON.parse(pkgRaw);
        const allDeps = [
            ...Object.keys(pkg.dependencies ?? {}),
            ...Object.keys(pkg.devDependencies ?? {}),
            ...Object.keys(pkg.peerDependencies ?? {}),
        ];
        return matchStackSignals(allDeps);
    }
    catch {
        return [];
    }
}
/**
 * Convert raw stack string[] (from ProjectKnowledge.stack or scan_project)
 * into typed StackSignal[]. Deduplicates by canonical name.
 */
export function matchStackSignals(stackItems, stackHints) {
    const seen = new Set();
    const signals = [];
    for (const raw of stackItems) {
        const name = normalizeSignal(raw);
        if (seen.has(name)) {
            continue;
        }
        seen.add(name);
        signals.push({ name, confidence: 'medium', source: 'project-scan' });
    }
    for (const hint of stackHints ?? []) {
        const name = normalizeSignal(hint);
        if (seen.has(name)) {
            continue;
        }
        seen.add(name);
        signals.push({ name, confidence: 'high', source: 'stack-hint' });
    }
    return signals;
}
//# sourceMappingURL=stack-matcher.js.map