/**
 * Main Path Integration Contract — 扫描基础设施（文件判定 + 调用证据检测 + 导入解析）
 */
import fs from "node:fs";
import fspath from "node:path";
import { debug } from "../../core/logger.js";
import { PRODUCTION_EXTENSIONS, NON_PRODUCTION_PATTERNS } from "./constants.js";
import { isExemptFile } from "./queries.js";
import { errorMessage } from "../../core/helpers.js";
/**
 * 判断文件是否为生产源码文件。
 */
export function isProductionFile(filePath, config) {
    const normalized = filePath.replace(/\\/g, "/");
    const ext = fspath.extname(normalized);
    if (!PRODUCTION_EXTENSIONS.has(ext)) {
        return { isProd: false, reason: `非生产扩展名: ${ext}` };
    }
    for (const p of NON_PRODUCTION_PATTERNS) {
        if (p.test(normalized)) {
            return { isProd: false, reason: `非生产文件: ${p.source}` };
        }
    }
    const exempt = isExemptFile(normalized);
    if (exempt.exempt) {
        return { isProd: false, reason: exempt.reason };
    }
    if (config) {
        const inScope = isInScope(normalized, config);
        if (!inScope) {
            return { isProd: false, reason: "不在项目 scope 范围内" };
        }
    }
    return { isProd: true };
}
function isInScope(normalizedPath, config) {
    const scopes = [];
    if (config.scope?.backend)
        scopes.push(...config.scope.backend);
    if (config.scope?.frontend)
        scopes.push(...config.scope.frontend);
    for (const repo of config.repos ?? []) {
        if (repo.scope)
            scopes.push(...repo.scope);
        scopes.push(repo.path.replace(/\\/g, "/"));
    }
    if (scopes.length === 0)
        return normalizedPath.startsWith("src/");
    return scopes.some((s) => {
        const ns = s.replace(/\\/g, "/").replace(/^\.\//, "");
        return normalizedPath.startsWith(ns);
    });
}
/**
 * 递归遍历目录，收集所有文件路径。
 */
export function walkDir(dir) {
    debug("主链路接入", "遍历目录 — " + dir);
    const results = [];
    if (!fs.existsSync(dir)) {
        debug("主链路接入", "遍历目录 — 目录不存在: " + dir);
        return results;
    }
    try {
        for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
            const full = fspath.join(dir, entry.name);
            if (entry.isDirectory())
                results.push(...walkDir(full));
            else
                results.push(full);
        }
    }
    catch (e) {
        debug("主链路接入", "遍历目录异常 — " + dir + ": " + (errorMessage(e)));
    }
    debug("主链路接入", "遍历目录完成 — " + dir + " 共 " + results.length + " 个文件");
    return results;
}
// ── 调用证据检测 ──
/** 从 TS/JS 导入语句中提取导入的符号名称 */
export function extractImportedNames(content, targetNorm) {
    const names = [];
    const modBasename = targetNorm.split("/").pop();
    const modJsExt = modBasename.replace(/\.ts$/, ".js");
    const escaped = modBasename.replace(/\./g, "\\.");
    const escapedJs = modJsExt.replace(/\./g, "\\.");
    const namedRe = new RegExp(`import\\s+\\{([^}]+)\\}\\s+from\\s+['"][^'"]*(?:${escaped}|${escapedJs})[^'"]*['"]`, "g");
    let m;
    while ((m = namedRe.exec(content)) !== null) {
        for (const name of m[1].split(",")) {
            const trimmed = name.trim().replace(/\s+as\s+\w+/, "").trim();
            if (trimmed)
                names.push(trimmed);
        }
    }
    const defaultRe = new RegExp(`import\\s+(\\w+)\\s+from\\s+['"][^'"]*(?:${escaped}|${escapedJs})[^'"]*['"]`, "g");
    while ((m = defaultRe.exec(content)) !== null)
        names.push(m[1]);
    const nsRe = new RegExp(`import\\s+\\*\\s+as\\s+(\\w+)\\s+from\\s+['"][^'"]*(?:${escaped}|${escapedJs})[^'"]*['"]`, "g");
    while ((m = nsRe.exec(content)) !== null)
        names.push(m[1]);
    return [...new Set(names)];
}
export function extractJavaImportedNames(content, targetFqn) {
    const names = [];
    const parts = targetFqn.split(".");
    const simpleName = parts[parts.length - 1];
    if (content.includes(simpleName))
        names.push(simpleName);
    return names;
}
export function extractGoImportedNames(_content, _pkgPath) {
    return [];
}
export function extractPythonImportedNames(content, modulePath) {
    const names = [];
    const parts = modulePath.split(".");
    const modName = parts[parts.length - 1];
    const asRe = new RegExp(`import\\s+${modName}\\s+as\\s+(\\w+)`);
    const asMatch = content.match(asRe);
    if (asMatch)
        names.push(asMatch[1]);
    if (content.includes(modName))
        names.push(modName);
    return [...new Set(names)];
}
export function detectTsCallEvidence(content, names) {
    const codeOnly = content.replace(/^import\s.+$/gm, "").replace(/^export\s+\{[^}]*\}\s+from\s+.+$/gm, "");
    const evidence = [];
    for (const name of names) {
        if (new RegExp(`\\b${name}\\s*\\(`).test(codeOnly))
            evidence.push(`${name}()`);
        if (new RegExp(`new\\s+${name}\\s*[\\(\\{]`).test(codeOnly))
            evidence.push(`new ${name}()`);
        if (new RegExp(`\\b${name}\\s*\\.`).test(codeOnly))
            evidence.push(`${name}.method()`);
        if (new RegExp(`<${name}[\\s/>]`).test(codeOnly))
            evidence.push(`<${name} />`);
    }
    return evidence;
}
export function detectJavaCallEvidence(content, names) {
    const evidence = [];
    for (const name of names) {
        const nameRe = name.replace(/\./g, "\\.");
        if (new RegExp(`(?:new\\s+)?${nameRe}\\s*\\(`).test(content)) {
            evidence.push(new RegExp(`new\\s+${nameRe}`).test(content) ? `new ${name}()` : `${name}()`);
        }
        if (new RegExp(`\\b${nameRe}\\s*\\.`).test(content))
            evidence.push(`${name}.method()`);
        if (new RegExp(`@(?:Inject|Autowired|Resource)\\b.*${nameRe}`).test(content))
            evidence.push(`@Inject ${name}`);
        if (new RegExp(`\\b${nameRe}\\s+\\w+[,)=]`).test(content))
            evidence.push(`${name} param`);
    }
    return evidence;
}
export function detectPythonCallEvidence(content, names) {
    const evidence = [];
    for (const name of names) {
        if (new RegExp(`\\b${name}\\s*\\(`).test(content))
            evidence.push(`${name}()`);
        if (new RegExp(`\\b${name}\\s*\\.`).test(content))
            evidence.push(`${name}.method()`);
    }
    return evidence;
}
export function detectGoCallEvidence(content, names) {
    const evidence = [];
    for (const name of names) {
        if (new RegExp(`\\b${name}\\s*\\.`).test(content))
            evidence.push(`${name}.Method()`);
        if (new RegExp(`\\b${name}\\s*\\(`).test(content))
            evidence.push(`${name}()`);
    }
    return evidence;
}
// ── 导入解析 ──
export function extractImportPaths(content, filePath) {
    debug("主链路接入", "提取导入路径 — " + filePath);
    const paths = [];
    const ext = fspath.extname(filePath);
    if (ext === ".java" || ext === ".kt") {
        const re = /import\s+([\w.]+)\s*;?/g;
        let m;
        while ((m = re.exec(content)) !== null)
            paths.push(m[1]);
    }
    else if (ext === ".go") {
        const singleRe = /import\s+"([^"]+)"/g;
        let m;
        while ((m = singleRe.exec(content)) !== null)
            paths.push(m[1]);
        const multiRe = /import\s*\(\s*([\s\S]*?)\)/g;
        while ((m = multiRe.exec(content)) !== null) {
            const inner = m[1];
            const lines = inner.match(/"([^"]+)"/g);
            if (lines)
                for (const l of lines)
                    paths.push(l.replace(/"/g, ""));
        }
    }
    else if (ext === ".py") {
        const fromRe = /from\s+([\w.]+)\s+import/g;
        let m;
        while ((m = fromRe.exec(content)) !== null)
            paths.push(m[1]);
        const importRe = /\bimport\s+([\w.]+)/g;
        while ((m = importRe.exec(content)) !== null)
            paths.push(m[1]);
    }
    else {
        const fromRe = /(?:import|export)\s+(?:type\s+)?[^'"]*?\s+from\s+['"]([^'"]+)['"]/g;
        let m;
        while ((m = fromRe.exec(content)) !== null)
            paths.push(m[1]);
        const reqRe = /require\s*\(\s*['"]([^'"]+)['"]\s*\)/g;
        while ((m = reqRe.exec(content)) !== null)
            paths.push(m[1]);
    }
    debug("主链路接入", "提取导入路径完成 — " + filePath + " 共 " + paths.length + " 条");
    return paths;
}
export function resolveImportPath(importSpecifier, fromFileRel, projectPath) {
    if (!importSpecifier.startsWith("."))
        return null;
    debug("主链路接入", "解析导入路径 — " + importSpecifier + " (from " + fromFileRel + ")");
    let spec = importSpecifier;
    const hasExt = /\.(ts|tsx|js|jsx)$/.test(spec);
    if (spec.endsWith(".js"))
        spec = spec.slice(0, -3);
    else if (spec.endsWith(".jsx"))
        spec = spec.slice(0, -4);
    const fromDir = fspath.dirname(fspath.resolve(projectPath, fromFileRel));
    const resolved = fspath.resolve(fromDir, spec);
    if (hasExt && fs.existsSync(resolved)) {
        return fspath.relative(projectPath, resolved).replace(/\\/g, "/");
    }
    for (const ext of [".ts", ".tsx", ".js", ".jsx"]) {
        const p = resolved + ext;
        if (fs.existsSync(p))
            return fspath.relative(projectPath, p).replace(/\\/g, "/");
    }
    for (const idx of ["index.ts", "index.tsx", "index.js", "index.jsx"]) {
        const p = fspath.join(resolved, idx);
        if (fs.existsSync(p))
            return fspath.relative(projectPath, p).replace(/\\/g, "/");
    }
    return null;
}
/** Normalize a module path by stripping extension for comparison */
export function normalizeModulePath(p) {
    return p.replace(/\.(ts|tsx|js|jsx)$/, "").replace(/\\/g, "/");
}
// ── Java/Kotlin 全限定名解析 ──
export function computeJavaFqn(filePath, projectPath) {
    debug("主链路接入", "计算Java全限定名 — " + filePath);
    let content;
    try {
        content = fs.readFileSync(fspath.join(projectPath, filePath), "utf-8");
    }
    catch (e) {
        debug("主链路接入", "计算Java全限定名失败 — " + filePath + ": " + (errorMessage(e)));
        return null;
    }
    const pkgMatch = content.match(/package\s+([\w.]+)\s*;?/);
    if (!pkgMatch) {
        debug("主链路接入", "计算Java全限定名 — 未找到 package 声明: " + filePath);
        return null;
    }
    const pkg = pkgMatch[1];
    const classMatch = content.match(/(?:public\s+)?(?:class|interface|enum|record)\s+(\w+)/);
    if (!classMatch) {
        debug("主链路接入", "计算Java全限定名 — 未找到类声明: " + filePath);
        return null;
    }
    const fqn = `${pkg}.${classMatch[1]}`;
    debug("主链路接入", "计算Java全限定名完成 — " + fqn);
    return fqn;
}
export function matchJavaFqn(importerContent, targetFqn) {
    const fqnImport = "import " + targetFqn;
    const wildcardImport = "import " + targetFqn.split(".").slice(0, -1).join(".") + ".*";
    return importerContent.includes(fqnImport + ";") ||
        importerContent.includes(fqnImport + "\n") ||
        importerContent.includes(wildcardImport + ";") ||
        importerContent.includes(wildcardImport + "\n");
}
export function computePythonModulePath(filePath) {
    const normalized = filePath.replace(/\\/g, "/");
    const noExt = normalized.replace(/\.py$/, "");
    const parts = noExt.split("/");
    const srcIdx = parts.findIndex(p => p === "src");
    if (srcIdx >= 0)
        return parts.slice(srcIdx + 1).join(".");
    return parts.join(".");
}
export function computeGoPkgPath(filePath) {
    return fspath.dirname(filePath.replace(/\\/g, "/")).replace(/\\/g, "/");
}
export function isOnlyReExport(content, targetNorm, ext) {
    if (ext === ".java" || ext === ".kt" || ext === ".go" || ext === ".py")
        return false;
    const modBasename = targetNorm.split("/").pop();
    const escaped = modBasename.replace(/\./g, "\\.");
    const escapedJs = modBasename.replace(/\.ts$/, ".js").replace(/\./g, "\\.");
    const reExportRe = new RegExp(`export\\s+(?:\\*|\\{[^}]*\\})\\s+from\\s+['"][^'"]*(?:${escaped}|${escapedJs})[^'"]*['"]`);
    if (!reExportRe.test(content))
        return false;
    const usageRe = new RegExp(`(?<!export\\s)(?<!from\\s['"])${modBasename.replace(/\./g, "\\.")}`, "g");
    const usageMatches = content.match(usageRe);
    return !usageMatches || usageMatches.length <= 2;
}
export function isRegistryOnlyImport(content, targetNorm, ext) {
    if (ext === ".java" || ext === ".kt" || ext === ".go" || ext === ".py")
        return false;
    const modBasename = targetNorm.split("/").pop();
    const modJsExt = modBasename.replace(/\.ts$/, ".js");
    const escaped = modBasename.replace(/\./g, "\\.");
    const escapedJs = modJsExt.replace(/\./g, "\\.");
    const regPatterns = [
        new RegExp(`\\.push\\s*\\(`, "s"),
        new RegExp(`register\\s*\\(`, "s"),
        new RegExp(`entries\\s*[:=]`, "s"),
    ];
    const hasRegistry = regPatterns.some(p => p.test(content));
    if (!hasRegistry)
        return false;
    const importLineRe = new RegExp(`import\\s+.*from\\s+['"][^'"]*(?:${escaped}|${escapedJs})[^'"]*['"]`);
    const lines = content.split("\n");
    let importCount = 0;
    let usageCount = 0;
    for (const line of lines) {
        if (importLineRe.test(line)) {
            importCount++;
            continue;
        }
        if (line.includes(modBasename))
            usageCount++;
    }
    return usageCount <= 1 && importCount > 0;
}
//# sourceMappingURL=scanning_infra.js.map