/**
 * Grep-only backend — minimum-viable fallback for `StructuralPrepass`.
 *
 * Walks the source tree with regex per language. No external tools, no
 * indexer, no SQLite. Useful for:
 *   - Tests / CI environments where CodeGraph isn't installed
 *   - Bootstrapping the adapter layer before CodeGraph is wired
 *   - Sanity-check baseline (a real backend should at least equal this)
 *
 * Capabilities: only the bare minimum (no call graph, no cross-file
 * resolution, no clustering). Adapters that need more should select a
 * richer backend.
 */
import { readFileSync } from 'fs';
import { extname } from 'path';
import { walkSourceTree, globToRegex } from './walk.js';
import { extractEmits, extractDbWrites, extractExternalCalls, extractThrows, extractAsyncBoundaries, countBranchPoints, extractCallNames, sliceBody, } from './method-patterns.js';
const LANG_PATTERNS = {
    typescript: {
        fileExtensions: ['.ts', '.tsx'],
        classRegex: /^(?:export\s+)?(?:default\s+)?(?:abstract\s+)?class\s+(\w+)/gm,
        methodRegex: /^\s+(?:public|private|protected|static|async)?\s*(?:async\s+)?(\w+)\s*\(/gm,
        interfaceRegex: /^(?:export\s+)?interface\s+(\w+)/gm,
        importRegex: /^import\s+(?:type\s+)?(?:\{([^}]+)\}|(\w+))\s+from\s+["']([^"']+)["']/gm,
    },
    javascript: {
        fileExtensions: ['.js', '.jsx', '.mjs', '.cjs'],
        classRegex: /^(?:export\s+)?(?:default\s+)?class\s+(\w+)/gm,
        methodRegex: /^\s+(?:async\s+)?(\w+)\s*\(/gm,
        importRegex: /^import\s+(?:\{([^}]+)\}|(\w+))\s+from\s+["']([^"']+)["']/gm,
    },
    python: {
        fileExtensions: ['.py'],
        classRegex: /^class\s+(\w+)/gm,
        methodRegex: /^\s+def\s+(\w+)\s*\(/gm,
        importRegex: /^(?:from\s+(\S+)\s+)?import\s+(.+)$/gm,
    },
    go: {
        fileExtensions: ['.go'],
        classRegex: /^type\s+(\w+)\s+struct/gm,
        methodRegex: /^func\s+\([^)]*\)\s+(\w+)\s*\(/gm,
        importRegex: /^import\s+(?:\(([\s\S]*?)\)|"([^"]+)")/gm,
    },
    java: {
        fileExtensions: ['.java'],
        // Java class declarations: optional `public` / `abstract` / `final`
        // modifiers before `class`. Annotations (like @Entity) live on the
        // line above; we match the class line itself, then typescript-decorators
        // walks backwards to find any preceding `@…` decorator block.
        classRegex: /^(?:public\s+|abstract\s+|final\s+|static\s+)*class\s+(\w+)/gm,
        // Java method declarations: access modifier + return type + name + (.
        // Captures the name as group 1; allows generic return types like
        // `List<Foo>`. The leading whitespace requirement keeps us inside a
        // class body (top-level class declarations are filtered above).
        methodRegex: /^\s+(?:public|private|protected|static|abstract|final|synchronized)\s+(?:[\w<>\[\],\s.]+\s+)?(\w+)\s*\(/gm,
        interfaceRegex: /^(?:public\s+|abstract\s+)*interface\s+(\w+)/gm,
        // Java imports: `import a.b.C;` or `import static a.b.C;` — captures the
        // fully-qualified path (everything between `import` and `;`).
        importRegex: /^import\s+(?:static\s+)?([\w.*]+);/gm,
    },
};
/**
 * Detect a language using grep-only's own per-language pattern set.
 * (Matches against fileExtensions inside LANG_PATTERNS — different from
 * walk.ts's `detectLanguage` which uses a broader extension set without
 * regex patterns.)
 */
function detectGrepLanguage(filePath) {
    const ext = extname(filePath).toLowerCase();
    for (const [lang, patterns] of Object.entries(LANG_PATTERNS)) {
        if (patterns.fileExtensions.includes(ext))
            return lang;
    }
    return null;
}
export class GrepOnlyBackend {
    sourceDir = '';
    files = [];
    /** Cache file→content to avoid re-reading on multiple calls. */
    fileCache = new Map();
    capabilities = {
        callGraph: false,
        crossFileResolution: false,
        componentClustering: false,
        fullTextSearch: false,
        fileWatching: false,
        routeExtraction: false, // engines 6.30.0+: GitNexus-native; walk fallback deferred (TODO #52)
        implementsExtraction: false, // engines 6.30.0+: GitNexus-native; walk fallback deferred
        validationSchemaExtraction: false, // engines 6.30.0+: GitNexus-native; walk fallback deferred
        processExtraction: false, // requires call-graph analysis we don't do
    };
    async init(sourceDir) {
        this.sourceDir = sourceDir;
        this.files = [];
        this.fileCache.clear();
    }
    async index() {
        const start = Date.now();
        // Walk the full filesystem; downstream listClasses / listMethods filter by
        // language, but listFiles needs to expose all files (e.g. schema.prisma).
        this.files = walkSourceTree(this.sourceDir);
        return {
            files: this.files.length,
            symbols: 0, // we don't pre-index symbols; queries scan on demand
            durationMs: Date.now() - start,
        };
    }
    async listFiles(filter) {
        let result = [...this.files];
        if (filter?.dir) {
            result = result.filter((f) => f.startsWith(filter.dir));
        }
        if (filter?.lang) {
            result = result.filter((f) => detectGrepLanguage(f) === filter.lang);
        }
        if (filter?.glob) {
            const re = globToRegex(filter.glob);
            result = result.filter((f) => re.test(f));
        }
        return result;
    }
    async listClasses(filter) {
        const out = [];
        for (const file of this.files) {
            if (filter?.dir && !file.startsWith(filter.dir))
                continue;
            const lang = detectGrepLanguage(file);
            if (!lang)
                continue;
            const patterns = LANG_PATTERNS[lang];
            const text = await this.fileSourceText(file);
            const lines = text.split('\n');
            const matches = [...text.matchAll(patterns.classRegex)];
            for (const m of matches) {
                const name = m[1];
                if (filter?.namePattern && !filter.namePattern.test(name))
                    continue;
                const idx = m.index ?? 0;
                const startLine = text.slice(0, idx).split('\n').length;
                // Crude end-line: scan forward for matching brace depth
                const endLine = this.findClassEnd(lines, startLine - 1, lang);
                out.push({
                    kind: 'class',
                    name,
                    qualifiedName: name, // grep-only doesn't track namespacing
                    filePath: file,
                    startLine,
                    endLine,
                    isExported: m[0].includes('export'),
                    language: lang,
                });
            }
        }
        return out;
    }
    async listMethods(filter) {
        const out = [];
        const classes = await this.listClasses();
        const targetClasses = filter?.class ? classes.filter((c) => c.qualifiedName === filter.class) : classes;
        for (const cls of targetClasses) {
            const lang = cls.language;
            const patterns = LANG_PATTERNS[lang];
            if (!patterns.methodRegex)
                continue;
            const text = await this.fileSourceText(cls.filePath);
            const lines = text.split('\n');
            // Slice the class body
            const bodyLines = lines.slice(cls.startLine - 1, cls.endLine);
            const body = bodyLines.join('\n');
            const matches = [...body.matchAll(patterns.methodRegex)];
            for (const m of matches) {
                const name = m[1];
                // Skip pseudo-methods like 'if', 'for', etc. (heuristic — keyword filter)
                // Skip constructors — they're framework injection sites, not business
                // logic, and including their bodies in MethodFactSheet leaks
                // framework-specific declared types (e.g. `Repository<X>` from
                // NestJS+TypeORM constructor signatures) into the prepass output.
                if (['constructor', 'if', 'for', 'while', 'switch', 'catch', 'return'].includes(name))
                    continue;
                if (filter?.nameIn && !filter.nameIn.includes(name))
                    continue;
                const lineOffset = body.slice(0, m.index ?? 0).split('\n').length;
                out.push({
                    kind: 'method',
                    name,
                    qualifiedName: `${cls.name}.${name}`,
                    filePath: cls.filePath,
                    startLine: cls.startLine + lineOffset - 1,
                    endLine: cls.startLine + lineOffset - 1, // grep-only doesn't trace function ends
                    language: lang,
                });
            }
        }
        return out;
    }
    async listInterfaces(filter) {
        const out = [];
        for (const file of this.files) {
            if (filter?.dir && !file.startsWith(filter.dir))
                continue;
            const lang = detectGrepLanguage(file);
            if (!lang)
                continue;
            const patterns = LANG_PATTERNS[lang];
            if (!patterns.interfaceRegex)
                continue;
            const text = await this.fileSourceText(file);
            const matches = [...text.matchAll(patterns.interfaceRegex)];
            for (const m of matches) {
                const name = m[1];
                if (filter?.namePattern && !filter.namePattern.test(name))
                    continue;
                const idx = m.index ?? 0;
                const startLine = text.slice(0, idx).split('\n').length;
                out.push({
                    kind: 'interface',
                    name,
                    qualifiedName: name,
                    filePath: file,
                    startLine,
                    endLine: startLine,
                    isExported: m[0].includes('export'),
                    language: lang,
                });
            }
        }
        return out;
    }
    async listImports(file) {
        const lang = detectGrepLanguage(file);
        if (!lang)
            return [];
        const patterns = LANG_PATTERNS[lang];
        if (!patterns.importRegex)
            return [];
        const text = await this.fileSourceText(file);
        const out = [];
        for (const m of text.matchAll(patterns.importRegex)) {
            // TS/JS: groups are (named imports, default import, module path)
            // Python: (from-module, names) or (names) for plain imports
            // Go: (block content, single-line module)
            if (lang === 'typescript' || lang === 'javascript') {
                const named = m[1] ? m[1].split(',').map((s) => s.trim()) : [];
                const def = m[2] ? [m[2]] : [];
                const mod = m[3];
                out.push({ fromFile: file, toModule: mod, symbols: [...named, ...def] });
            }
            else if (lang === 'python') {
                const fromMod = m[1] || '';
                const names = (m[2] || '').split(',').map((s) => s.trim());
                out.push({ fromFile: file, toModule: fromMod, symbols: names });
            }
            else if (lang === 'go') {
                const mod = m[2] || (m[1] || '').replace(/"/g, '').trim();
                if (mod)
                    out.push({ fromFile: file, toModule: mod, symbols: [] });
            }
        }
        return out;
    }
    async fileSourceText(file) {
        if (this.fileCache.has(file))
            return this.fileCache.get(file);
        const text = readFileSync(file, 'utf8');
        this.fileCache.set(file, text);
        return text;
    }
    /**
     * Extract per-method facts via regex over the method body.
     *
     * Locates the method (parses class containing `qualifiedName`'s class part,
     * scans for the method by name), pulls the body via brace-depth tracking,
     * runs each pattern extractor on it.
     */
    async getMethodDetails(qualifiedName) {
        // Parse "ClassName.methodName" — we only support single-level qualified
        // names for v1; namespaced classes (e.g. SomeModule.ClassName.method) fall
        // through as if `ClassName.methodName`.
        const lastDot = qualifiedName.lastIndexOf('.');
        if (lastDot < 0)
            return null;
        const className = qualifiedName.slice(0, lastDot);
        const methodName = qualifiedName.slice(lastDot + 1);
        const classes = await this.listClasses({ namePattern: new RegExp(`^${className}$`) });
        if (classes.length === 0)
            return null;
        const cls = classes[0];
        const text = await this.fileSourceText(cls.filePath);
        const lines = text.split('\n');
        // Find the method declaration line within the class body
        const classBody = lines.slice(cls.startLine - 1, cls.endLine);
        let methodStartIdx = -1;
        let methodSig = '';
        for (let i = 0; i < classBody.length; i++) {
            const line = classBody[i];
            // Match: `[modifiers] [async] methodName(...)` — capture the method's name
            const re = new RegExp(`^\\s+(?:public|private|protected|static|async)?\\s*(?:async\\s+)?${methodName}\\s*\\(`);
            if (re.test(line)) {
                methodStartIdx = i;
                // Capture the signature line (single-line approximation; multi-line
                // signatures roll up to the next `{`)
                let sig = line.trim();
                let j = i;
                while (!sig.includes('{') && j + 1 < classBody.length) {
                    j++;
                    sig += ' ' + classBody[j].trim();
                }
                methodSig = sig.replace(/\s*\{\s*$/, '').trim();
                break;
            }
        }
        if (methodStartIdx < 0)
            return null;
        // Find body via brace depth — scan from the first `{` after methodStartIdx
        const bodyStartFileLineIdx = cls.startLine - 1 + methodStartIdx;
        let depth = 0;
        let started = false;
        let bodyEndFileLineIdx = bodyStartFileLineIdx;
        for (let i = bodyStartFileLineIdx; i < lines.length; i++) {
            for (const ch of lines[i]) {
                if (ch === '{') {
                    depth++;
                    started = true;
                }
                else if (ch === '}') {
                    depth--;
                    if (started && depth === 0) {
                        bodyEndFileLineIdx = i;
                        i = lines.length; // break outer loop
                        break;
                    }
                }
            }
        }
        const bodyLines = lines.slice(bodyStartFileLineIdx, bodyEndFileLineIdx + 1);
        const body = bodyLines.join('\n');
        // Resolve called names → Symbol[] when possible (look up in our class index)
        const callNames = extractCallNames(body);
        const allMethods = await this.listMethods();
        const calls = [];
        for (const name of callNames) {
            const match = allMethods.find((m) => m.name === name);
            if (match)
                calls.push(match);
        }
        return {
            qualifiedName,
            signature: methodSig,
            body,
            filePath: cls.filePath,
            startLine: bodyStartFileLineIdx + 1,
            endLine: bodyEndFileLineIdx + 1,
            language: cls.language,
            calls,
            emits: extractEmits(body),
            dbWrites: extractDbWrites(body),
            externalCalls: extractExternalCalls(body),
            throws: extractThrows(body),
            asyncBoundaries: extractAsyncBoundaries(body),
            branchPoints: countBranchPoints(body),
            bodyTextSliced: sliceBody(body),
        };
    }
    // ────────────────────────────────────────────────────────────────────
    // Internals
    // ────────────────────────────────────────────────────────────────────
    /** Find the closing line of a class block by tracking brace depth. */
    findClassEnd(lines, startLineIdx, lang) {
        if (lang === 'python') {
            // Indentation-based: find the next line at the SAME or LESS indent than the class line.
            const classLine = lines[startLineIdx];
            const baseIndent = classLine.match(/^(\s*)/)?.[1].length ?? 0;
            for (let i = startLineIdx + 1; i < lines.length; i++) {
                const line = lines[i];
                if (line.trim() === '')
                    continue;
                const indent = line.match(/^(\s*)/)?.[1].length ?? 0;
                if (indent <= baseIndent)
                    return i; // 1-indexed, but i is 0-indexed; this is the first line AFTER the class
            }
            return lines.length;
        }
        // C-style: track brace depth
        let depth = 0;
        let started = false;
        for (let i = startLineIdx; i < lines.length; i++) {
            for (const char of lines[i]) {
                if (char === '{') {
                    depth++;
                    started = true;
                }
                else if (char === '}') {
                    depth--;
                    if (started && depth === 0)
                        return i + 1; // 1-indexed line number
                }
            }
        }
        return lines.length;
    }
}
//# sourceMappingURL=grep-only.js.map