import fs from "node:fs/promises";
import fss from "node:fs";
import path from "node:path";
import Handlebars from "handlebars";
import { debug } from "../core/logger.js";
import { getContractDecision } from "./template_asset_contract_registry.js";
import { recordConsumptionTrace } from "./consumption_trace_store.js";
import { getContractByPath } from "./template_asset_contract_registry.js";
/**
 * 生成代码骨架 — 遍历模板集，编译渲染每个 .hbs 模板，映射到目标路径并检测冲突。
 * @param input - 脚手架输入，包含用户意图、分类结果、项目配置和项目路径
 * @returns 脚手架结果，包含生成的文件列表、模块名、摘要和冲突警告
 */
export async function generateScaffold(input) {
    const { intent, classification, config, projectPath } = input;
    debug("脚手架生成", `开始生成代码骨架，意图: "${intent.slice(0, 50)}..."`);
    // 提取模块名
    const moduleName = extractModuleName(intent);
    // 确定模板集
    const templateSets = resolveTemplateSets(config);
    // 渲染模板
    const files = [];
    const warnings = [];
    if (templateSets.length === 0) {
        warnings.push("当前技术栈没有适用的内置脚手架模板，请提供匹配技术栈的自定义模板合同");
    }
    for (const templateSet of templateSets) {
        const templateDir = resolveTemplateDir(templateSet, config);
        if (!fss.existsSync(templateDir)) {
            warnings.push(`模板目录不存在: ${templateDir}`);
            debug("脚手架生成", `模板目录不存在: ${templateDir}`);
            continue;
        }
        const templateFiles = await fs.readdir(templateDir);
        for (const tplFile of templateFiles) {
            if (!tplFile.endsWith(".hbs"))
                continue;
            // 合同门禁: 内置和自定义模板统一校验
            const isBuiltin = templateDir.includes(path.join("templates", "scaffolds"));
            if (isBuiltin) {
                const relPath = `templates/scaffolds/${templateSet}/${tplFile}`;
                const decision = getContractDecision(relPath);
                if (decision.decision === "unregistered") {
                    recordConsumptionTrace({ consumer: "scaffold", asset_id: relPath, asset_path: relPath, contract_decision: "unregistered", fields_consumed: ["asset_path", "contract_decision", "template_vars"], matched_reason: "未注册脚手架模板", task_id_or_route: "generateScaffold", consumed_at: new Date().toISOString() });
                    warnings.push(`未注册脚手架模板: ${tplFile} — 必须先在 explicit_asset_registry 注册`);
                    debug("脚手架生成", `未注册模板被阻断: ${relPath}`);
                    continue;
                }
                if (decision.decision === "denied" && decision.contract?.asset_visibility !== "scaffold_only") {
                    recordConsumptionTrace({ consumer: "scaffold", asset_id: decision.contract?.asset_id ?? relPath, asset_path: relPath, contract_decision: "denied", fields_consumed: ["asset_path", "contract_decision", "template_vars"], matched_reason: "模板可见性不符", task_id_or_route: "generateScaffold", consumed_at: new Date().toISOString() });
                    warnings.push(`模板可见性不符: ${tplFile} — 预期 scaffold_only`);
                    debug("脚手架生成", `模板可见性不符: ${relPath}`);
                    continue;
                }
                if (decision.contract && !matchesBuiltinTechStack(decision.contract.applicable_tech_stack, config)) {
                    recordConsumptionTrace({ consumer: "scaffold", asset_id: decision.contract.asset_id, asset_path: relPath, contract_decision: "denied", fields_consumed: ["asset_path", "contract_decision", "applicable_tech_stack"], matched_reason: "内置脚手架技术栈不匹配", task_id_or_route: "generateScaffold", consumed_at: new Date().toISOString() });
                    warnings.push(`模板技术栈不匹配: ${tplFile} — 仅适用于 ${decision.contract.applicable_tech_stack.join(", ")}`);
                    continue;
                }
                recordConsumptionTrace({ consumer: "scaffold", asset_id: decision.contract?.asset_id ?? relPath, asset_path: relPath, contract_decision: "allowed", fields_consumed: ["asset_path", "contract_decision", "template_vars"], matched_reason: "内置模板通过合同门禁", task_id_or_route: "generateScaffold", consumed_at: new Date().toISOString() });
            }
            else {
                // 自定义模板: 必须有显式合同，缺失则阻断
                const customAssetId = `custom:${templateSet}/${tplFile}`;
                const contract = config.scaffold?.contracts?.[templateSet];
                if (!contract) {
                    recordConsumptionTrace({ consumer: "scaffold", asset_id: customAssetId, asset_path: customAssetId, contract_decision: "denied", fields_consumed: ["asset_path", "contract_decision", "template_vars"], matched_reason: `自定义模板集 "${templateSet}" 缺少显式合同: 在 config.scaffold.contracts["${templateSet}"] 中定义 required_template_vars, tech_stack, output_boundary, forbidden_patterns`, task_id_or_route: "generateScaffold", consumed_at: new Date().toISOString() });
                    warnings.push(`自定义模板集 "${templateSet}" 缺少显式合同 — 请在 config.scaffold.contracts["${templateSet}"] 中定义: required_template_vars, tech_stack, output_boundary`);
                    debug("脚手架生成", `自定义模板集缺少合同被阻断: ${templateSet}`);
                    continue;
                }
                const contractResult = validateCustomContract(customAssetId, tplFile, contract, config);
                if (!contractResult.passed) {
                    recordConsumptionTrace({ consumer: "scaffold", asset_id: customAssetId, asset_path: customAssetId, contract_decision: "denied", fields_consumed: ["asset_path", "contract_decision", "template_vars"], matched_reason: contractResult.reason, task_id_or_route: "generateScaffold", consumed_at: new Date().toISOString() });
                    warnings.push(contractResult.reason);
                    debug("脚手架生成", `自定义模板被合同阻断: ${customAssetId} — ${contractResult.reason}`);
                    continue;
                }
            }
            const templateContent = await fs.readFile(path.join(templateDir, tplFile), "utf-8");
            // 自定义模板: 必需变量内容校验
            if (!isBuiltin) {
                const customAssetId = `custom:${templateSet}/${tplFile}`;
                const contract = config.scaffold?.contracts?.[templateSet];
                if (!contract)
                    continue; // 前面已阻断
                const requiredVars = contract.required_template_vars ?? [];
                // 检查模板内容是否引用了所有必需变量
                const missing = requiredVars.filter(v => !templateContent.includes(`{{${v}}}`) && !templateContent.includes(v));
                if (missing.length > 0) {
                    recordConsumptionTrace({ consumer: "scaffold", asset_id: customAssetId, asset_path: customAssetId, contract_decision: "denied", fields_consumed: ["asset_path", "contract_decision", "template_vars"], matched_reason: `模板缺少必需变量引用: ${missing.join(", ")}`, task_id_or_route: "generateScaffold", consumed_at: new Date().toISOString() });
                    warnings.push(`自定义模板 ${tplFile} 缺少必需变量引用: ${missing.join(", ")}`);
                    continue;
                }
                recordConsumptionTrace({ consumer: "scaffold", asset_id: customAssetId, asset_path: customAssetId, contract_decision: "allowed", fields_consumed: ["asset_path", "contract_decision", "template_vars"], matched_reason: "自定义模板通过合同校验", task_id_or_route: "generateScaffold", consumed_at: new Date().toISOString() });
            }
            const compiled = Handlebars.compile(templateContent, {
                knownHelpersOnly: true,
                knownHelpers: { if: true, each: true, with: true, unless: true, lookup: true, log: true },
            });
            const vars = buildTemplateVars(moduleName, config, templateSet, classification);
            // 内置模板: 校验 required_template_vars 与模板真实引用变量一致
            if (isBuiltin) {
                const relPath = `templates/scaffolds/${templateSet}/${tplFile}`;
                const contract = getContractByPath(relPath);
                if (contract?.required_template_vars && contract.required_template_vars.length > 0) {
                    for (const reqVar of contract.required_template_vars) {
                        if (!(reqVar in vars) || !vars[reqVar]) {
                            warnings.push(`缺少合同要求的模板变量: ${reqVar} — 模板 ${tplFile} 无法渲染`);
                            debug("脚手架生成", `变量缺失: ${reqVar} 在 ${relPath}`);
                        }
                    }
                }
            }
            const rendered = compiled(vars);
            // 自定义模板: 输出边界校验
            if (!isBuiltin) {
                const contract = config.scaffold?.contracts?.[templateSet];
                if (!contract)
                    continue;
                const boundaryResult = validateOutputBoundary(tplFile, rendered, contract);
                if (!boundaryResult.passed) {
                    warnings.push(boundaryResult.reason);
                    debug("脚手架生成", `输出边界违反: ${tplFile} — ${boundaryResult.reason}`);
                    continue;
                }
            }
            // 计算目标路径
            const outputName = tplFile.replace(".hbs", "").replaceAll("{{ModuleName}}", vars.ModuleName);
            const targetPath = resolveTargetPath(outputName, vars, templateSet, config);
            // 冲突检测
            const fullPath = path.join(projectPath, targetPath);
            if (fss.existsSync(fullPath)) {
                warnings.push(`文件已存在: ${targetPath}`);
            }
            files.push({
                path: targetPath,
                content: rendered,
                description: describeFile(outputName, vars.ModuleName),
                is_template: rendered.includes("{{"),
                template_vars: rendered.includes("{{") ? { ModuleName: vars.ModuleName } : undefined,
            });
        }
    }
    debug("脚手架生成", `完成，模块 ${moduleName}，生成 ${files.length} 个文件${warnings.length > 0 ? `，${warnings.length} 个冲突` : ""}`);
    return {
        task_id: "",
        module_name: moduleName,
        files,
        summary: `为模块 ${moduleName} 生成 ${files.length} 个文件${warnings.length > 0 ? `，${warnings.length} 个冲突` : ""}`,
        total_files: files.length,
        warnings,
    };
}
/**
 * 从用户意图中提取模块名 — 优先匹配中文实体映射，其次提取英文 PascalCase，最后回退到关键词。
 * @param intent - 用户意图描述文本
 * @returns 提取的模块名（PascalCase），无法识别时返回 "Module"
 */
// ── 模块名提取 ──
function extractModuleName(intent) {
    // 中文实体到英文 PascalCase 的映射表
    const entityMap = {
        "订单": "Order", "商品": "Product", "用户": "User",
        "支付": "Payment", "权限": "Permission", "角色": "Role",
        "菜单": "Menu", "库存": "Inventory", "退款": "Refund",
        "发票": "Invoice", "地址": "Address", "评论": "Comment",
        "通知": "Notification", "消息": "Message", "购物车": "Cart",
        "配置": "Config", "日志": "AuditLog", "报表": "Report",
        "导出": "Export", "导入": "Import",
    };
    for (const [cn, en] of Object.entries(entityMap)) {
        if (intent.includes(cn))
            return en;
    }
    // 尝试从意图中提取英文 PascalCase 标识符
    const pascalMatch = intent.match(/[A-Z][a-z]+(?:[A-Z][a-z]+)*/);
    if (pascalMatch)
        return pascalMatch[0];
    // 回退: 取第一个非功能动词的关键词，转 PascalCase
    const words = intent.split(/\s+/).filter((w) => w.length > 1);
    // 排除功能动词（新建/创建/添加/初始化/模块/功能），取有实质意义的词
    const word = words.find((w) => !/^(新建|创建|添加|初始化|模块|功能|加|加个|加一个)$/.test(w));
    if (word)
        return toPascalCase(word);
    return "Module";
}
/** 将字符串转为 PascalCase — 支持 kebab-case 和 snake_case 转换 */
function toPascalCase(str) {
    return str
        .replace(/[-_](\w)/g, (_, c) => c.toUpperCase())
        .replace(/^\w/, (c) => c.toUpperCase());
}
/** 英文复数化 — 支持不规则名词（Person→People）和常规规则（s/es/ies） */
function pluralize(name) {
    // 不规则名词映射表
    const irregulars = {
        Person: "People", Child: "Children", Man: "Men", Woman: "Women",
        Mouse: "Mice", Goose: "Geese", Tooth: "Teeth", Foot: "Feet",
    };
    if (irregulars[name])
        return irregulars[name];
    // 以 s/sh/ch/x 结尾的词加 "es"
    if (name.endsWith("s") || name.endsWith("sh") || name.endsWith("ch") || name.endsWith("x")) {
        return name + "es";
    }
    // 辅音+y 结尾的词变 y 为 ies
    if (name.endsWith("y") && !/[aeiou]y$/i.test(name)) {
        return name.slice(0, -1) + "ies";
    }
    return name + "s";
}
// ── 模板集解析 ──
/** 根据项目技术栈解析适用的模板集 — 当前支持 spring-boot 和 react。 */
function resolveTemplateSets(config) {
    const sets = [];
    const backendFramework = config.tech_stack.backend.framework.toLowerCase();
    const frontendFramework = config.tech_stack.frontend.framework.toLowerCase();
    if (backendFramework.includes("spring"))
        sets.push("spring-boot");
    if (frontendFramework.includes("react"))
        sets.push("react");
    return sets;
}
function matchesBuiltinTechStack(required, config) {
    if (required.length === 0)
        return true;
    const actual = [
        config.tech_stack.backend.lang,
        config.tech_stack.backend.framework,
        config.tech_stack.frontend.lang,
        config.tech_stack.frontend.framework,
    ].filter(Boolean).map((value) => value.toLowerCase());
    return required.some((value) => actual.some((actualValue) => actualValue.includes(value.toLowerCase())));
}
/**
 * 解析模板目录路径 — 优先使用项目自定义模板目录，回退到内置模板目录。
 * @param templateSet - 模板集名称（如 "spring-boot"、"react"）
 * @param config - 项目配置，可包含自定义模板目录
 * @returns 模板目录的绝对路径
 */
function resolveTemplateDir(templateSet, config) {
    // 优先使用项目自定义模板目录
    if (config?.scaffold?.custom_templates_dir) {
        const customDir = path.resolve(config.scaffold.custom_templates_dir, templateSet);
        if (fss.existsSync(customDir))
            return customDir;
    }
    // 从 npm 包路径解析内置脚手架模板，使用 import.meta.dirname
    // src/engine/templates/ → ../../../ → 项目根 → templates/scaffolds
    const builtinDir = path.resolve(import.meta.dirname, "..", "..", "..", "templates", "scaffolds", templateSet);
    if (fss.existsSync(builtinDir))
        return builtinDir;
    // 最终回退仍指向包内路径
    return builtinDir;
}
/**
 * 构建 Handlebars 模板变量 — 包括模块名各种命名风格（Pascal/camel/snake/SCREAMING_SNAKE）和项目元信息。
 * @param moduleName - 模块名（PascalCase）
 * @param config - 项目配置
 * @param _templateSet - 模板集名称（预留）
 * @param classification - 可选的任务分类结果（用于注入风险/复杂度等级）
 * @returns 模板变量键值对
 */
function buildTemplateVars(moduleName, config, _templateSet, classification) {
    const basePackage = config.scaffold?.base_package ?? "com.example.app";
    const module = moduleName.charAt(0).toLowerCase() + moduleName.slice(1);
    const apiVersion = config.scaffold?.templates?.match(/v(\d+)/)?.[1] ?? "1"; // 从模板配置提取 API 版本号
    return {
        ModuleName: moduleName,
        moduleName: module,
        module_name: moduleName.replace(/([A-Z])/g, "_$1").toLowerCase().replace(/^_/, ""),
        MODULE_NAME: moduleName.replace(/([A-Z])/g, "_$1").toUpperCase().replace(/^_/, ""),
        basePackage,
        packagePath: basePackage.replace(/\./g, "/"),
        moduleNamePlural: pluralize(moduleName),
        moduleNameLower: module.toLowerCase(),
        apiVersion: `v${apiVersion}`,
        basePath: `/api/v${apiVersion}/${module}`,
        riskLevel: classification?.risk ?? "low",
        complexityLevel: classification?.complexity ?? "low",
        date: new Date().toISOString().split("T")[0],
        author: "SoloForge",
    };
}
/**
 * 根据模板集和文件后缀解析目标文件路径 — Spring Boot 按分层结构映射，React 按页面/服务/Hook 分类。
 * @param outputName - 模板输出文件名（已去除 .hbs 后缀）
 * @param vars - 模板变量
 * @param templateSet - 模板集名称
 * @param config - 项目配置
 * @returns 目标文件的相对路径
 */
function resolveTargetPath(outputName, vars, templateSet, _config) {
    if (templateSet === "spring-boot") {
        const srcPath = `src/main/java/${vars.packagePath}`;
        const testPath = `src/test/java/${vars.packagePath}`;
        if (outputName.endsWith("Test.java")) {
            return `${testPath}/${outputName}`;
        }
        // Spring Boot 分层目录映射: 文件后缀 → 所在分层目录
        const layerMap = {
            "Controller.java": "controller",
            "Service.java": "service",
            "ServiceImpl.java": "service/impl",
            "Mapper.java": "mapper",
            "Entity.java": "entity",
            "DTO.java": "dto",
            "ErrorCode.java": "enums",
            "PageResponse.java": "dto",
        };
        for (const [suffix, layer] of Object.entries(layerMap)) {
            if (outputName.endsWith(suffix)) {
                return `${srcPath}/${layer}/${outputName}`;
            }
        }
        // 文档类文件
        if (outputName.endsWith("ApiContract.md")) {
            return `docs/api/${outputName}`;
        }
        return `${srcPath}/${outputName}`;
    }
    if (templateSet === "react") {
        const srcPath = "src";
        if (outputName.endsWith(".test.tsx")) {
            return `${srcPath}/pages/__tests__/${outputName}`;
        }
        if (outputName.endsWith("service.ts")) {
            return `${srcPath}/services/${outputName}`;
        }
        if (outputName.endsWith("types.ts")) {
            return `${srcPath}/types/${outputName}`;
        }
        if (outputName.startsWith("use") && outputName.endsWith(".ts")) {
            return `${srcPath}/pages/${vars.moduleName}/${outputName}`;
        }
        if (outputName.endsWith("README.md")) {
            return `docs/modules/${outputName}`;
        }
        return `${srcPath}/pages/${vars.moduleName}/${outputName}`;
    }
    return outputName;
}
/** 校验自定义模板的项目级合同（tech_stack + 必需变量声明阶段） */
function validateCustomContract(_assetId, tplFile, contract, config) {
    // tech_stack 校验
    if (contract.tech_stack && contract.tech_stack.length > 0) {
        const backend = config.tech_stack.backend.framework.toLowerCase();
        const frontend = config.tech_stack.frontend.framework.toLowerCase();
        const matched = contract.tech_stack.some(ts => backend.includes(ts.toLowerCase()) || frontend.includes(ts.toLowerCase()));
        if (!matched) {
            return { passed: false, reason: `自定义模板 ${tplFile} 要求技术栈 [${contract.tech_stack.join(", ")}]，与当前项目 (${backend}/${frontend}) 不匹配` };
        }
    }
    return { passed: true, reason: "" };
}
/** 校验渲染后输出的边界约束 */
function validateOutputBoundary(tplFile, rendered, contract) {
    const boundary = contract.output_boundary;
    if (!boundary)
        return { passed: true, reason: "" };
    // 文件大小校验
    if (boundary.max_file_size && rendered.length > boundary.max_file_size) {
        return { passed: false, reason: `自定义模板 ${tplFile} 输出超过最大限制: ${rendered.length} > ${boundary.max_file_size} 字节` };
    }
    // 禁止内容模式校验
    if (boundary.forbidden_patterns) {
        for (const pattern of boundary.forbidden_patterns) {
            try {
                if (new RegExp(pattern).test(rendered)) {
                    return { passed: false, reason: `自定义模板 ${tplFile} 输出包含禁止内容: /${pattern}/` };
                }
            }
            catch {
                // 无效正则视为不匹配
            }
        }
    }
    return { passed: true, reason: "" };
}
/**
 * 生成文件中文描述 — 根据输出文件名后缀和前缀匹配预定义描述表。
 */
function describeFile(outputName, moduleName) {
    // 文件后缀 → 中文描述映射表
    const descMap = {
        "Controller.java": `${moduleName} REST 控制器`,
        "Service.java": `${moduleName} 服务接口`,
        "ServiceImpl.java": `${moduleName} 服务实现`,
        "Mapper.java": `${moduleName} 数据访问层`,
        "Entity.java": `${moduleName} 实体类`,
        "DTO.java": `${moduleName} 数据传输对象`,
        "ServiceTest.java": `${moduleName} 服务测试`,
        "ErrorCode.java": `${moduleName} 错误码枚举`,
        "PageResponse.java": `${moduleName} 分页响应封装`,
        "ApiContract.md": `${moduleName} API 契约文档`,
        "README.md": `${moduleName} 模块使用说明`,
    };
    // 优先精确匹配后缀，其次按文件类型推断描述
    for (const [suffix, desc] of Object.entries(descMap)) {
        if (outputName.endsWith(suffix))
            return desc;
    }
    if (outputName.endsWith(".tsx"))
        return `${moduleName} 页面组件`;
    if (outputName.startsWith("use") && outputName.endsWith(".ts"))
        return `${moduleName} 数据 Hook`;
    if (outputName.endsWith(".ts"))
        return `${moduleName} TypeScript 模块`;
    return `${moduleName} 文件`;
}
//# sourceMappingURL=scaffolder.js.map