import { readFile } from 'node:fs/promises';
import { join, relative } from 'node:path';
import { glob } from 'glob';
import { emptyImpactMap, estimateImpactBudget, mergeImpactMaps } from './impact-map.js';
const SOURCE_EXTENSIONS = '{ts,tsx,js,jsx,mjs,cjs,py,go,rs,java,rb,cs}';
function normalizePath(path) {
    return path.replace(/\\/g, '/');
}
function extractPaths(text) {
    const matches = text.match(/\b(?:src|tests|packages|lib)\/[\w./-]+\.[A-Za-z0-9]+\b/g) ?? [];
    return [...new Set(matches.map(normalizePath))];
}
function extractImports(content) {
    const imports = new Set();
    const patterns = [
        /from\s+['"]([^'"]+)['"]/g,
        /import\s*\(\s*['"]([^'"]+)['"]\s*\)/g,
        /require\(\s*['"]([^'"]+)['"]\s*\)/g,
    ];
    for (const pattern of patterns) {
        for (const match of content.matchAll(pattern)) {
            if (match[1]?.startsWith('.')) {
                imports.add(match[1]);
            }
        }
    }
    return [...imports];
}
function extractSymbols(content, path) {
    const symbols = [];
    const lines = content.split('\n');
    const pattern = /\b(?:export\s+)?(?:async\s+)?(?:function|class|interface|type|const)\s+([A-Za-z_][\w]*)/;
    for (let index = 0; index < lines.length; index += 1) {
        const match = pattern.exec(lines[index] ?? '');
        if (match?.[1]) {
            symbols.push({ name: match[1], path, line: index + 1, confidence: 0.75 });
        }
    }
    return symbols;
}
export class PlanuContextGraphProvider {
    async buildImpactMap(input) {
        const candidates = await this.resolveCandidateFiles(input);
        if (candidates.length === 0) {
            return emptyImpactMap();
        }
        const maps = await Promise.all(candidates.map((path) => this.mapFile(input.projectPath, path)));
        return mergeImpactMaps(maps, input.tokenBudget);
    }
    async resolveCandidateFiles(input) {
        const fromChanged = (input.changedFiles ?? []).map(normalizePath);
        const fromSpec = input.specId ? await this.readSpecReferencedPaths(input) : [];
        const direct = [...new Set([...fromChanged, ...fromSpec])].filter(Boolean);
        if (direct.length > 0) {
            return direct.slice(0, estimateImpactBudget(input.tokenBudget) * 2);
        }
        const maxFiles = estimateImpactBudget(input.tokenBudget);
        return (await glob(`**/*.${SOURCE_EXTENSIONS}`, {
            cwd: input.projectPath,
            nodir: true,
            ignore: ['**/node_modules/**', '**/dist/**', '**/.git/**', '**/coverage/**'],
        }))
            .map(normalizePath)
            .slice(0, maxFiles);
    }
    async readSpecReferencedPaths(input) {
        const specFiles = await glob(`planu/specs/${input.specId}-*/spec.md`, {
            cwd: input.projectPath,
            nodir: true,
        });
        const specPath = specFiles[0];
        if (!specPath) {
            return [];
        }
        const content = await readFile(join(input.projectPath, specPath), 'utf-8').catch(() => '');
        return extractPaths(content);
    }
    async mapFile(projectPath, path) {
        const normalized = normalizePath(path);
        const absolute = join(projectPath, normalized);
        const content = await readFile(absolute, 'utf-8').catch(() => '');
        const imports = extractImports(content);
        return {
            files: [{ path: normalized, reason: 'direct-context-candidate', confidence: 0.9 }],
            symbols: extractSymbols(content, normalized),
            edges: imports.map((target) => ({
                from: normalized,
                to: normalizePath(relative(projectPath, join(absolute, '..', target))),
                type: 'import',
            })),
            truncated: false,
        };
    }
}
export function createContextGraphProvider() {
    return new PlanuContextGraphProvider();
}
//# sourceMappingURL=context-graph-provider.js.map