import fs from "node:fs/promises";
import path from "node:path";
import { debug } from "./core/logger.js";
import { isPathInAnyScope } from "./core/path_scope_utils.js";
/**
 * 执行影响范围分析。
 * 从用户意图中提取业务实体，搜索可能受影响的文件，
 * 并与声明的允许范围进行对比，判断作用域是否充分。
 * @param input - 影响分析输入，包含意图、分类结果、扩展结果、项目配置和路径
 * @returns 影响分析结果，包含受影响文件列表、越界文件和建议
 */
export async function analyzeImpact(input) {
    debug("影响分析", "analyzeImpact() 开始执行");
    const { intent, classification, expansion, config, projectPath } = input;
    // 提取业务实体名
    const entities = extractEntities(intent);
    if (entities.length === 0) {
        return {
            task_id: "",
            intent,
            affected_files: [],
            declared_scope: expansion.scope.allowed_paths,
            out_of_scope_files: [],
            scope_sufficient: true,
            recommendation: "proceed",
            summary: "未识别到明确的业务实体，无法进行影响分析",
        };
    }
    // 在受影响仓库中搜索相关文件
    const affectedFiles = await searchAffectedFiles(entities, classification.affected_repos, config, projectPath);
    debug("影响分析", "搜索到受影响文件", affectedFiles.length, "个");
    // 对比声明范围
    const declaredScope = expansion.scope.allowed_paths;
    const outOfScope = affectedFiles.filter((f) => !isPathInAnyScope(f.path, declaredScope));
    const scopeSufficient = outOfScope.length === 0;
    const recommendation = deriveRecommendation(outOfScope);
    debug("影响分析", "作用域" + (scopeSufficient ? "充分" : "不足") + "，建议: " + recommendation + "，越界文件: " + outOfScope.length);
    const expandSuggestion = recommendation === "expand_scope"
        ? deduplicatePaths(outOfScope.map((f) => path.posix.dirname(f.path)))
        : undefined;
    return {
        task_id: "",
        intent,
        affected_files: affectedFiles,
        declared_scope: declaredScope,
        out_of_scope_files: outOfScope,
        scope_sufficient: scopeSufficient,
        recommendation,
        summary: formatSummary(affectedFiles.length, outOfScope.length, recommendation),
        expand_suggestion: expandSuggestion,
    };
}
/**
 * 从意图文本中提取业务实体名称。
 * 支持中文业务名词匹配和英文 PascalCase 识别。
 * @param intent - 用户意图文本
 * @returns 提取到的实体名称列表（去重后）
 */
function extractEntities(intent) {
    // 从意图中提取业务实体（中文词组或英文驼峰词）
    const entities = [];
    // 中文实体提取: 常见业务名词
    const businessTerms = [
        "订单", "商品", "用户", "支付", "权限", "角色", "菜单",
        "导出", "导入", "报表", "配置", "日志", "通知", "消息",
        "库存", "购物车", "退款", "发票", "地址", "评论",
    ];
    for (const term of businessTerms) {
        if (intent.includes(term) && !entities.includes(term)) {
            entities.push(term);
        }
    }
    // 英文实体提取: PascalCase 词（匹配如 OrderService、UserController 等）
    const pascalMatches = intent.match(/[A-Z][a-z]+(?:[A-Z][a-z]+)*/g);
    if (pascalMatches) {
        for (const m of pascalMatches) {
            if (!entities.includes(m))
                entities.push(m);
        }
    }
    return entities;
}
/**
 * 在受影响仓库中递归搜索与实体名相关的文件。
 * 对每个仓库列出所有源文件，通过路径匹配判断关联性。
 * @param entities - 业务实体名称列表
 * @param affectedRepos - 受影响的仓库名称列表
 * @param config - 项目配置（包含仓库信息）
 * @param projectPath - 项目根路径
 * @returns 匹配到的受影响文件列表（含置信度和匹配原因）
 */
async function searchAffectedFiles(entities, affectedRepos, config, projectPath) {
    const results = [];
    for (const repo of affectedRepos) {
        const repoConfig = config.repos.find((r) => r.name === repo);
        if (!repoConfig)
            continue;
        const repoPath = path.resolve(projectPath, repoConfig.path);
        let files;
        try {
            files = await listFilesRecursive(repoPath);
        }
        catch (e) {
            debug("影响分析", "仓库文件列表获取失败 —", e);
            continue;
        }
        for (const filePath of files) {
            const relativePath = path.relative(projectPath, filePath).replace(/\\/g, "/");
            const matchInfo = matchEntityInPath(relativePath, entities);
            if (matchInfo) {
                results.push({
                    path: relativePath,
                    reason: matchInfo.reason,
                    confidence: matchInfo.confidence,
                });
            }
        }
    }
    return results;
}
/**
 * 递归列出目录下所有文件。
 * 自动跳过隐藏目录和常见的构建产物目录。
 * @param dir - 起始目录路径
 * @param maxDepth - 最大递归深度，默认 6 层，防止遍历过深
 * @param depth - 当前递归深度（内部递归使用）
 * @returns 目录下所有文件的绝对路径列表
 */
async function listFilesRecursive(dir, maxDepth = 6, depth = 0) {
    if (depth > maxDepth)
        return []; // 超出最大深度，停止递归
    const entries = await fs.readdir(dir, { withFileTypes: true });
    const files = [];
    for (const entry of entries) {
        // 跳过隐藏目录和常见构建/依赖目录
        if (entry.name.startsWith(".") || ["node_modules", "target", "build", "dist", ".git"].includes(entry.name)) {
            continue;
        }
        const fullPath = path.join(dir, entry.name);
        if (entry.isDirectory()) {
            files.push(...await listFilesRecursive(fullPath, maxDepth, depth + 1));
        }
        else {
            files.push(fullPath);
        }
    }
    return files;
}
/**
 * 判断文件路径是否与业务实体相关，并返回匹配的置信度。
 *
 * 置信度规则：
 * - high: 文件名直接包含实体名且带有 Entity/DO/DTO/VO/PO/Model 后缀
 * - medium: 文件名包含实体名且为 Service/Controller/Mapper/Repository/Component 或测试文件
 * - low: 文件名或路径中包含实体名，但无明确结构关联
 *
 * @param filePath - 文件相对路径
 * @param entities - 业务实体名称列表
 * @returns 匹配结果（包含原因和置信度），未匹配返回 null
 */
function matchEntityInPath(filePath, entities) {
    const basename = path.posix.basename(filePath, path.posix.extname(filePath));
    const lowerPath = filePath.toLowerCase();
    for (const entity of entities) {
        const entityLower = entity.toLowerCase();
        // 高置信度: 文件名直接包含实体名且为数据模型类
        if (basename.toLowerCase().includes(entityLower)) {
            // 数据模型后缀 — 直接对应实体定义
            if (/Entity|DO|DTO|VO|PO|Model/.test(basename)) {
                return { reason: `${basename} 直接对应实体 ${entity}`, confidence: "high" };
            }
            // 业务层后缀 — 操作该实体的服务或控制器
            if (/Service|Controller|Mapper|Repository|Component/.test(basename)) {
                return { reason: `${basename} 操作实体 ${entity}`, confidence: "medium" };
            }
            // 测试文件 — 间接关联该实体
            if (/test|spec/i.test(basename)) {
                return { reason: `${basename} 测试实体 ${entity}`, confidence: "medium" };
            }
            return { reason: `${basename} 可能涉及实体 ${entity}`, confidence: "low" };
        }
        // 路径中包含实体名
        if (lowerPath.includes(entityLower)) {
            return { reason: `路径包含 ${entity}`, confidence: "low" };
        }
    }
    return null;
}
/**
 * 根据越界文件的置信度推导后续建议。
 *
 * 决策规则：
 * - 无越界文件 → proceed（直接执行）
 * - 存在高或中置信度越界文件 → expand_scope（建议扩大范围）
 * - 仅有低置信度越界文件 → confirm（建议人工确认）
 *
 * @param outOfScope - 越界文件列表
 * @returns 建议类型
 */
function deriveRecommendation(outOfScope) {
    if (outOfScope.length === 0)
        return "proceed";
    const hasHighConfidence = outOfScope.some((f) => f.confidence === "high");
    const hasMediumConfidence = outOfScope.some((f) => f.confidence === "medium");
    if (hasHighConfidence || hasMediumConfidence)
        return "expand_scope";
    return "confirm";
}
/** 对目录路径列表去重 */
function deduplicatePaths(dirs) {
    return [...new Set(dirs)];
}
/**
 * 格式化影响分析的摘要文本。
 * @param affected - 受影响文件总数
 * @param outOfScope - 越界文件数量
 * @param recommendation - 建议类型
 * @returns 人类可读的摘要字符串
 */
function formatSummary(affected, outOfScope, recommendation) {
    if (recommendation === "proceed") {
        return `影响范围分析完成: ${affected} 个相关文件，全部在声明范围内`;
    }
    const labels = {
        expand_scope: `影响范围超出声明范围: ${outOfScope} 个文件不在当前 scope 内，建议扩大作用域`,
        confirm: `影响范围可能超出: ${outOfScope} 个低置信度文件不在 scope 内，建议确认`,
    };
    return labels[recommendation] ?? "分析完成";
}
//# sourceMappingURL=impact_analyzer.js.map