"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
    var ownKeys = function(o) {
        ownKeys = Object.getOwnPropertyNames || function (o) {
            var ar = [];
            for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
            return ar;
        };
        return ownKeys(o);
    };
    return function (mod) {
        if (mod && mod.__esModule) return mod;
        var result = {};
        if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
        __setModuleDefault(result, mod);
        return result;
    };
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.selectPreferredStackCandidate = selectPreferredStackCandidate;
exports.detectStacks = detectStacks;
const fs = __importStar(require("node:fs"));
const path = __importStar(require("node:path"));
const stackAdapters_1 = require("../stackAdapters");
const ignoredDirectoryNames = new Set([
    '.git',
    '.archpilot',
    '.archpilot_',
    '.vscode-test',
    'tests',
    'test',
    '__tests__',
    'fixtures',
    'smoke',
    'docs',
    'doc',
    'reports',
    'report',
    'badges',
    'examples',
    'node_modules',
    'dist',
    'build',
    'out',
    'coverage',
    '.next',
    'target',
    'vendor',
    '.venv',
    'venv',
    '__pycache__',
]);
const backendStackIds = new Set([
    'spring',
    'express',
    'fastapi',
    'django',
    'flask',
    'dotnet',
    'nestjs',
    'java',
    'python',
    'php',
    'laravel',
    'go',
    'ruby_rails',
    'kotlin',
    'kotlin_spring',
    'node_typescript',
    'node_javascript',
]);
const frontendStackIds = new Set(['react', 'nextjs', 'angular', 'vue']);
const genericNodeStackIds = new Set(['node_typescript', 'node_javascript']);
function normalizeRelativePath(value) {
    return value.replaceAll('\\', '/');
}
function sortUnique(values) {
    return [...new Set(values)].sort((left, right) => left.localeCompare(right));
}
function isSupportingNodeToolingStack(stack, selected) {
    return genericNodeStackIds.has(stack) &&
        selected.adapter.category === 'framework' &&
        selected.adapter.ecosystem !== 'node';
}
function addEvidence(evidence, value) {
    if (!evidence.includes(value)) {
        evidence.push(value);
    }
}
function isIgnoredPath(ignoreMatcher, relativePath) {
    return ignoreMatcher?.isIgnored(normalizeRelativePath(relativePath)) ?? false;
}
function safeReadDirectoryEntries(directoryPath) {
    try {
        return fs.readdirSync(directoryPath, { withFileTypes: true });
    }
    catch {
        return [];
    }
}
function safeReadJsonFile(filePath) {
    try {
        return JSON.parse(fs.readFileSync(filePath, { encoding: 'utf8' }));
    }
    catch {
        return undefined;
    }
}
function scanWorkspaceForFileNames(workspaceRoot, targetFileNames, maxDepth = 6, ignoreMatcher) {
    const pathsByFileName = targetFileNames.reduce((acc, fileName) => {
        acc[fileName] = [];
        return acc;
    }, {});
    const filePaths = [];
    const extensionCounts = {};
    const directories = new Set(['']);
    const queue = [
        { absolutePath: workspaceRoot, relativePath: '', depth: 0 },
    ];
    while (queue.length > 0) {
        const current = queue.shift();
        if (!current) {
            continue;
        }
        const entries = safeReadDirectoryEntries(current.absolutePath)
            .sort((left, right) => left.name.localeCompare(right.name));
        for (const entry of entries) {
            const absoluteEntryPath = path.join(current.absolutePath, entry.name);
            const relativeEntryPath = current.relativePath.length > 0
                ? `${current.relativePath}/${entry.name}`
                : entry.name;
            if (isIgnoredPath(ignoreMatcher, relativeEntryPath)) {
                continue;
            }
            if (entry.isDirectory()) {
                directories.add(normalizeRelativePath(relativeEntryPath));
                if (current.depth >= maxDepth || ignoredDirectoryNames.has(entry.name)) {
                    continue;
                }
                queue.push({
                    absolutePath: absoluteEntryPath,
                    relativePath: relativeEntryPath,
                    depth: current.depth + 1,
                });
                continue;
            }
            if (!entry.isFile()) {
                continue;
            }
            filePaths.push(normalizeRelativePath(relativeEntryPath));
            if (Object.prototype.hasOwnProperty.call(pathsByFileName, entry.name)) {
                pathsByFileName[entry.name].push(normalizeRelativePath(relativeEntryPath));
            }
            const extension = path.extname(entry.name).toLowerCase();
            if (extension.length > 0) {
                extensionCounts[extension] = (extensionCounts[extension] ?? 0) + 1;
            }
        }
    }
    return {
        filePaths,
        pathsByFileName,
        extensionCounts,
        directories,
    };
}
function parsePackageJsonDependencies(workspaceRoot, packageJsonPaths) {
    const summaries = [];
    for (const packageJsonPath of packageJsonPaths) {
        const absolutePath = path.join(workspaceRoot, ...packageJsonPath.split('/'));
        const parsed = safeReadJsonFile(absolutePath);
        if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
            continue;
        }
        const typed = parsed;
        const dependencyFields = ['dependencies', 'devDependencies', 'peerDependencies', 'optionalDependencies'];
        const dependencies = new Set();
        for (const fieldName of dependencyFields) {
            const value = typed[fieldName];
            if (!value || typeof value !== 'object' || Array.isArray(value)) {
                continue;
            }
            for (const dependencyName of Object.keys(value)) {
                dependencies.add(dependencyName);
            }
        }
        summaries.push({
            path: packageJsonPath,
            dependencies,
        });
    }
    return summaries;
}
function confidenceWeight(confidence) {
    if (confidence === 'high') {
        return 3;
    }
    if (confidence === 'medium') {
        return 2;
    }
    return 1;
}
function compareCandidates(left, right) {
    if (left.result.confidence === 'high' &&
        right.result.confidence === 'high' &&
        left.adapter.category === 'framework' &&
        right.adapter.category === 'generic-language' &&
        left.adapter.ecosystem === right.adapter.ecosystem) {
        return -1;
    }
    if (left.result.confidence === 'high' &&
        right.result.confidence === 'high' &&
        left.adapter.category === 'generic-language' &&
        right.adapter.category === 'framework' &&
        left.adapter.ecosystem === right.adapter.ecosystem) {
        return 1;
    }
    const confidenceDiff = confidenceWeight(right.result.confidence) - confidenceWeight(left.result.confidence);
    if (confidenceDiff !== 0) {
        return confidenceDiff;
    }
    const scoreDiff = right.result.score - left.result.score;
    if (scoreDiff !== 0) {
        return scoreDiff;
    }
    const priorityDiff = right.adapter.priority - left.adapter.priority;
    if (priorityDiff !== 0) {
        return priorityDiff;
    }
    return left.adapter.id.localeCompare(right.adapter.id);
}
function selectPreferredStackCandidate(candidates) {
    if (candidates.length === 0) {
        return undefined;
    }
    return [...candidates].sort(compareCandidates)[0];
}
function collectTargetFileNames() {
    const names = new Set(['package.json', 'tsconfig.json']);
    for (const adapter of (0, stackAdapters_1.getSmartInitDetectionAdapters)()) {
        for (const fileName of adapter.getDefaults().configFiles) {
            names.add(fileName);
        }
        for (const fileName of adapter.manifestDetectionHints) {
            names.add(fileName);
        }
    }
    return [...names].sort((left, right) => left.localeCompare(right));
}
function uniqueInOrder(values) {
    const seen = new Set();
    const ordered = [];
    for (const value of values) {
        if (seen.has(value)) {
            continue;
        }
        seen.add(value);
        ordered.push(value);
    }
    return ordered;
}
function buildDetectionContext(workspaceRoot, filePaths, pathsByFileName, extensionCounts, directories, packageSummaries) {
    const fileTextCache = new Map();
    return {
        pathsByFileName,
        hasFile: (fileName) => (pathsByFileName[fileName]?.length ?? 0) > 0,
        getPaths: (fileName) => [...(pathsByFileName[fileName] ?? [])],
        hasPackageDependency: (dependencyName) => packageSummaries.some((summary) => summary.dependencies.has(dependencyName)),
        hasFileExtension: (extension) => (extensionCounts[extension.toLowerCase()] ?? 0) > 0,
        hasDirectory: (relativePath) => directories.has(normalizeRelativePath(relativePath)),
        hasFileMatchingPattern: (fileName, pattern) => {
            const paths = pathsByFileName[fileName] ?? [];
            for (const relativePath of paths) {
                let contents = fileTextCache.get(relativePath);
                if (contents === undefined && !fileTextCache.has(relativePath)) {
                    try {
                        const absolutePath = path.join(workspaceRoot, ...relativePath.split('/'));
                        contents = fs.readFileSync(absolutePath, { encoding: 'utf8' });
                    }
                    catch {
                        contents = undefined;
                    }
                    fileTextCache.set(relativePath, contents);
                }
                if (typeof contents === 'string' && pattern.test(contents)) {
                    return true;
                }
            }
            return false;
        },
        hasAnyPathMatchingPattern: (pathPattern) => filePaths.some((relativePath) => pathPattern.test(relativePath)),
        hasAnyFileMatchingPattern: (pathPattern, pattern) => {
            for (const relativePath of filePaths) {
                if (!pathPattern.test(relativePath)) {
                    continue;
                }
                let contents = fileTextCache.get(relativePath);
                if (contents === undefined && !fileTextCache.has(relativePath)) {
                    try {
                        const absolutePath = path.join(workspaceRoot, ...relativePath.split('/'));
                        contents = fs.readFileSync(absolutePath, { encoding: 'utf8' });
                    }
                    catch {
                        contents = undefined;
                    }
                    fileTextCache.set(relativePath, contents);
                }
                if (typeof contents === 'string' && pattern.test(contents)) {
                    return true;
                }
            }
            return false;
        },
    };
}
function toDetectedStack(adapterId) {
    switch (adapterId) {
        case 'node_typescript':
        case 'express':
        case 'nestjs':
        case 'java':
        case 'spring':
        case 'fastapi':
        case 'django':
        case 'flask':
        case 'dotnet':
        case 'python':
        case 'php':
        case 'laravel':
        case 'go':
        case 'ruby_rails':
        case 'kotlin':
        case 'kotlin_spring':
        case 'react':
        case 'nextjs':
        case 'angular':
        case 'vue':
        case 'terraform':
        case 'ansible':
        case 'other':
            return adapterId;
        default:
            return undefined;
    }
}
function resolveLanguages(stacks) {
    const languages = new Set();
    if (stacks.includes('java')) {
        languages.add('java');
    }
    if (stacks.includes('spring')) {
        languages.add('java');
    }
    if (stacks.includes('kotlin') || stacks.includes('kotlin_spring')) {
        languages.add('kotlin');
    }
    if (stacks.includes('dotnet')) {
        languages.add('csharp');
    }
    if (stacks.includes('python')) {
        languages.add('python');
    }
    if (stacks.includes('fastapi')) {
        languages.add('python');
    }
    if (stacks.includes('django')) {
        languages.add('python');
    }
    if (stacks.includes('flask')) {
        languages.add('python');
    }
    if (stacks.includes('php')) {
        languages.add('php');
    }
    if (stacks.includes('laravel')) {
        languages.add('php');
    }
    if (stacks.includes('ruby_rails')) {
        languages.add('ruby');
    }
    if (stacks.includes('go')) {
        languages.add('go');
    }
    if (stacks.includes('node_typescript')) {
        languages.add('typescript');
        languages.add('javascript');
    }
    else if (stacks.includes('node_javascript')) {
        languages.add('javascript');
    }
    if (stacks.includes('nestjs')) {
        languages.add('typescript');
        languages.add('javascript');
    }
    if (stacks.includes('express')) {
        languages.add('javascript');
        if (stacks.includes('node_typescript')) {
            languages.add('typescript');
        }
    }
    if (stacks.includes('react') || stacks.includes('nextjs') || stacks.includes('angular') || stacks.includes('vue')) {
        languages.add(stacks.includes('node_typescript') ? 'typescript' : 'javascript');
    }
    return sortUnique(languages);
}
function resolveOverallConfidence(candidates) {
    const high = candidates.filter((candidate) => candidate.result.confidence === 'high').length;
    const medium = candidates.filter((candidate) => candidate.result.confidence === 'medium').length;
    if (high >= 2) {
        return 'high';
    }
    if (high >= 1 || medium >= 2) {
        return 'medium';
    }
    return 'low';
}
function detectStacks(workspaceRoot, options) {
    const targetFileNames = collectTargetFileNames();
    const scanResult = scanWorkspaceForFileNames(workspaceRoot, targetFileNames, 6, options?.ignoreMatcher);
    const pathsByFileName = scanResult.pathsByFileName;
    const packageJsonPaths = sortUnique(pathsByFileName['package.json'] ?? []);
    const packageSummaries = parsePackageJsonDependencies(workspaceRoot, packageJsonPaths);
    const detectionContext = buildDetectionContext(workspaceRoot, scanResult.filePaths, pathsByFileName, scanResult.extensionCounts, scanResult.directories, packageSummaries);
    const candidates = (0, stackAdapters_1.getSmartInitDetectionAdapters)()
        .map((adapter) => ({ adapter, result: adapter.detect(detectionContext) }))
        .filter((entry) => !!entry.result)
        .sort(compareCandidates);
    const selected = selectPreferredStackCandidate(candidates);
    if (!selected) {
        const fallbackAdapter = (0, stackAdapters_1.getStackAdapterById)('other');
        const fallbackResult = {
            adapterId: fallbackAdapter.id,
            confidence: 'low',
            score: 1,
            evidence: ['No known stack signatures detected; using generic fallback adapter'],
        };
        return {
            stacks: ['other'],
            backendStacks: [],
            frontendStacks: [],
            languages: [],
            confidence: 'low',
            evidence: [...fallbackResult.evidence],
            selectedAdapterId: fallbackAdapter.id,
            selectedAdapterCategory: fallbackAdapter.category,
            selectedAdapterPriority: fallbackAdapter.priority,
            selectedAdapterScore: fallbackResult.score,
            candidates: [fallbackResult],
        };
    }
    const detectedStacks = [];
    for (const candidate of candidates) {
        const mapped = toDetectedStack(candidate.result.adapterId);
        if (mapped) {
            detectedStacks.push(mapped);
        }
    }
    const hasPackageJson = packageJsonPaths.length > 0;
    const hasTsConfig = (pathsByFileName['tsconfig.json']?.length ?? 0) > 0;
    if (hasPackageJson && !hasTsConfig) {
        detectedStacks.push('node_javascript');
    }
    const stacks = uniqueInOrder(detectedStacks);
    const backendStacks = stacks.filter((stack) => backendStackIds.has(stack) && !isSupportingNodeToolingStack(stack, selected));
    const frontendStacks = stacks.filter((stack) => frontendStackIds.has(stack));
    const evidence = [];
    for (const candidate of candidates) {
        for (const entry of candidate.result.evidence) {
            addEvidence(evidence, entry);
        }
    }
    addEvidence(evidence, `Selected adapter '${selected.adapter.id}' via deterministic ranking (confidence > score > priority > id).`);
    return {
        stacks,
        backendStacks,
        frontendStacks,
        languages: resolveLanguages(stacks),
        confidence: resolveOverallConfidence(candidates),
        evidence,
        selectedAdapterId: selected.adapter.id,
        selectedAdapterCategory: selected.adapter.category,
        selectedAdapterPriority: selected.adapter.priority,
        selectedAdapterScore: selected.result.score,
        candidates: candidates.map((candidate) => ({ ...candidate.result })),
    };
}
//# sourceMappingURL=stackDetection.js.map