/**
 * 模板清单 I/O — 哈希计算、清单构建、持久化。
 *
 * 供 CLI sync-templates 和测试消费。
 * 不改动已有引擎函数签名，纯新增工具层。
 */
import { createHash } from "node:crypto";
import fs from "node:fs";
import path from "node:path";
import { findConsumableAssetByPath } from "./consumable_asset_registry.js";
import { getContractDecision } from "./template_asset_contract_registry.js";
import { recordConsumptionTrace } from "./consumption_trace_store.js";
import { safeJsonParse } from "../core/helpers.js";
// ── 哈希工具 ──
/** 计算内容的 SHA-256 十六进制摘要 */
export function sha256Hex(content) {
    return createHash("sha256").update(content).digest("hex");
}
/** 计算单个文件的 SHA-256，文件不存在返回 null */
export function hashFile(filePath) {
    try {
        const content = fs.readFileSync(filePath);
        return sha256Hex(content);
    }
    catch {
        return null;
    }
}
/** 批量计算文件哈希，跳过不存在的文件 */
export function computeFileHashes(filePaths) {
    const result = new Map();
    for (const p of filePaths) {
        const h = hashFile(p);
        if (h !== null)
            result.set(p, h);
    }
    return result;
}
// ── 文件写入工具 ──
/** 递归创建目录并写入文件 */
export function writeFileSyncRecursive(filePath, content) {
    fs.mkdirSync(path.dirname(filePath), { recursive: true });
    fs.writeFileSync(filePath, content);
}
/** 递归扫描目录下所有 .md/.yaml/.yml 文件 */
function walkDirRecursive(dir) {
    const results = [];
    const stack = [dir];
    while (stack.length > 0) {
        const current = stack.pop();
        for (const entry of fs.readdirSync(current, { withFileTypes: true })) {
            const absPath = path.join(current, entry.name);
            if (entry.isDirectory()) {
                stack.push(absPath);
                continue;
            }
            if (/\.(md|yaml|yml)$/.test(entry.name))
                results.push(absPath);
        }
    }
    return results;
}
export function buildTemplateManifestFromSource(sourceDir, packageVersion) {
    const { manifest } = buildTemplateManifestWithStats(sourceDir, packageVersion);
    return manifest;
}
export function buildTemplateManifestWithStats(sourceDir, packageVersion) {
    const manifest = [];
    const stats = { included: 0, skipped_internal: 0, skipped_unregistered: 0, skipped_scaffold: [] };
    // 递归扫描 templates/ 下按类型组织的知识子目录
    const knowledgeSubDirs = ["internal/rules", "procedures", "rules/shared", "checklists", "artifacts/shared", "domain", "internal/patterns", "profiles"];
    for (const subDir of knowledgeSubDirs) {
        const knowledgeDir = path.join(sourceDir, subDir);
        if (!fs.existsSync(knowledgeDir))
            continue;
        const knowledgeFiles = walkDirRecursive(knowledgeDir);
        for (const absSource of knowledgeFiles) {
            const relFromSubDir = path.relative(path.join(sourceDir, subDir), absSource).replace(/\\/g, "/");
            const relFromKnowledge = `${subDir}/${relFromSubDir}`;
            const relSource = `templates/${relFromKnowledge}`;
            const file = path.basename(absSource);
            // 合同门禁: 逐文件调用 getContractDecision
            const decision = getContractDecision(relSource);
            if (decision.decision === "denied") {
                recordConsumptionTrace({ consumer: "sync_templates", asset_id: decision.contract?.asset_id ?? relSource, asset_path: relSource, contract_decision: "denied", fields_consumed: ["asset_path", "contract_decision", "source_hash"], matched_reason: "内部资产跳过", task_id_or_route: "buildTemplateManifest", consumed_at: new Date().toISOString() });
                stats.skipped_internal++;
                continue;
            }
            if (decision.decision === "unregistered") {
                recordConsumptionTrace({ consumer: "sync_templates", asset_id: relSource, asset_path: relSource, contract_decision: "unregistered", fields_consumed: ["asset_path", "contract_decision", "source_hash"], matched_reason: "未注册资产跳过", task_id_or_route: "buildTemplateManifest", consumed_at: new Date().toISOString() });
                stats.skipped_unregistered++;
                continue;
            }
            const sourceHash = hashFile(absSource) ?? "";
            const asset = findConsumableAssetByPath(relSource);
            const assetDir = path.dirname(relFromKnowledge);
            const targetPath = `.soloforge/knowledge/${relFromKnowledge}`;
            // 推导 sovereignty
            const isCore = relFromKnowledge.startsWith("internal/patterns/") ||
                (asset?.consumption_mode === "required" && asset?.asset_kind === "capability_knowledge_doc");
            manifest.push({
                asset_id: asset?.id ?? `tpl-${assetDir}-${file}`,
                asset_kind: asset?.asset_kind ?? "knowledge_template",
                source_path: relSource,
                target_path: targetPath,
                template_version: packageVersion,
                source_hash: sourceHash,
                installed_hash: "",
                installed_at: "",
                source_package_version: packageVersion,
                sovereignty: isCore ? "core" : "normal",
            });
            recordConsumptionTrace({ consumer: "sync_templates", asset_id: asset?.id ?? `tpl-${assetDir}-${file}`, asset_path: relSource, contract_decision: "allowed", fields_consumed: ["asset_path", "contract_decision", "source_hash"], matched_reason: "清单收录", task_id_or_route: "buildTemplateManifest", consumed_at: new Date().toISOString() });
            stats.included++;
        }
    }
    // 扫描全局模式（项目级副本）
    const patternsDir = path.join(sourceDir, "patterns");
    if (fs.existsSync(patternsDir)) {
        const files = fs.readdirSync(patternsDir).filter((f) => f.endsWith(".md"));
        for (const file of files) {
            const relSource = `templates/patterns/${file}`;
            const absSource = path.join(patternsDir, file);
            const decision = getContractDecision(relSource);
            if (decision.decision === "denied") {
                recordConsumptionTrace({ consumer: "sync_templates", asset_id: decision.contract?.asset_id ?? relSource, asset_path: relSource, contract_decision: "denied", fields_consumed: ["asset_path", "contract_decision", "source_hash"], matched_reason: "内部资产跳过", task_id_or_route: "buildTemplateManifest", consumed_at: new Date().toISOString() });
                stats.skipped_internal++;
                continue;
            }
            if (decision.decision === "unregistered") {
                recordConsumptionTrace({ consumer: "sync_templates", asset_id: relSource, asset_path: relSource, contract_decision: "unregistered", fields_consumed: ["asset_path", "contract_decision", "source_hash"], matched_reason: "未注册资产跳过", task_id_or_route: "buildTemplateManifest", consumed_at: new Date().toISOString() });
                stats.skipped_unregistered++;
                continue;
            }
            const sourceHash = hashFile(absSource) ?? "";
            const asset = findConsumableAssetByPath(relSource);
            manifest.push({
                asset_id: asset?.id ?? `pat-${file}`,
                asset_kind: asset?.asset_kind ?? "global_pattern",
                source_path: relSource,
                target_path: `.soloforge/knowledge/patterns/${file}`,
                template_version: packageVersion,
                source_hash: sourceHash,
                installed_hash: "",
                installed_at: "",
                source_package_version: packageVersion,
                sovereignty: "normal",
            });
            recordConsumptionTrace({ consumer: "sync_templates", asset_id: asset?.id ?? `pat-${file}`, asset_path: relSource, contract_decision: "allowed", fields_consumed: ["asset_path", "contract_decision", "source_hash"], matched_reason: "清单收录", task_id_or_route: "buildTemplateManifest", consumed_at: new Date().toISOString() });
            stats.included++;
        }
    }
    return { manifest, stats };
}
// ── 清单持久化 ──
const MANIFEST_FILE = ".soloforge/manifest.json";
/** 读取项目已安装清单，不存在返回 null */
export function loadProjectManifest(projectPath) {
    const filePath = path.join(projectPath, MANIFEST_FILE);
    try {
        const content = fs.readFileSync(filePath, "utf-8");
        return safeJsonParse(content) ?? null;
    }
    catch {
        return null;
    }
}
/** 写入项目已安装清单 */
export function saveProjectManifest(projectPath, manifest) {
    const filePath = path.join(projectPath, MANIFEST_FILE);
    writeFileSyncRecursive(filePath, JSON.stringify(manifest, null, 2));
}
// ── 版本文件 ──
const VERSION_FILE = ".soloforge/version.json";
/** 读取项目版本信息，不存在返回 null */
export function loadProjectVersion(projectPath) {
    try {
        const content = fs.readFileSync(path.join(projectPath, VERSION_FILE), "utf-8");
        return safeJsonParse(content) ?? null;
    }
    catch {
        return null;
    }
}
/** 写入项目版本信息 */
export function saveProjectVersion(projectPath, version) {
    writeFileSyncRecursive(path.join(projectPath, VERSION_FILE), JSON.stringify(version, null, 2));
}
//# sourceMappingURL=template_manifest_io.js.map