import fs from "node:fs/promises";
import os from "node:os";
import path from "node:path";
import YAML from "yaml";
import { z } from "zod";
import { detectFingerprint, generateConfigDraft } from "../engine/config/zero_config_init.js";
import { debug, userWarn } from "../engine/core/logger.js";
import { DEFAULT_PROJECT_NAME } from "../engine/core/config_defaults.js";
// 每个进程只输出一次 config.yaml 缺失提示，避免重复
let _inferredConfigWarningShown = false;
/** 当前配置 Schema 版本号 */
const CURRENT_SCHEMA_VERSION = 1;
/** 配置迁移注册表，键为源版本号 */
const MIGRATIONS = {
    // 0 → 1: 无格式变更，标记初始版本
    0: (config) => config,
};
/**
 * 迁移配置到最新版本（内部方法）。
 * @description 按版本号顺序执行所有待应用的迁移，并在完成后更新 schema_version。
 * @param raw - 原始配置数据
 * @returns 迁移后的配置数据
 */
function migrateConfig(raw) {
    if (!raw || typeof raw !== "object")
        return raw;
    const version = raw.schema_version ?? 0;
    let config = raw;
    for (let v = version; v < CURRENT_SCHEMA_VERSION; v++) {
        const migrate = MIGRATIONS[v];
        if (migrate)
            config = migrate(config);
    }
    config.schema_version = CURRENT_SCHEMA_VERSION;
    return config;
}
/**
 * 项目配置加载与验证模块。
 * @description 负责 SoloForge 项目配置的加载、验证和默认值填充。
 *              从 .soloforge/config.yaml 读取 YAML 配置，使用 Zod 进行严格校验。
 *              支持旧格式（project.name 嵌套）的兼容处理，以及基于路径模式的产品自动检测。
 */
// ── 配置 Schema ──
/** 构建命令验证 Schema：build/test/full 三项必填 */
const BuildCommandsSchema = z.object({
    build: z.string(),
    test: z.string(),
    full: z.string(),
});
/** 仓库配置验证 Schema */
const RepoConfigSchema = z.object({
    name: z.string().min(1, "仓库 name 不能为空"),
    path: z.string().min(1, "仓库 path 不能为空"),
    lang: z.string().min(1, "仓库 lang 不能为空"),
    framework: z.string().min(1, "仓库 framework 不能为空"),
    scope: z.array(z.string()).min(1, "仓库 scope 不能为空数组"),
});
/** 项目配置顶层验证 Schema，涵盖技术栈、产品、仓库、构建命令、作用域等字段 */
const ProjectConfigSchema = z.object({
    schema_version: z.number().optional(),
    name: z.string().min(1, "name 不能为空"),
    tech_stack: z.object({
        backend: z.object({
            lang: z.string(),
            framework: z.string(),
            version: z.string(),
        }),
        frontend: z.object({
            lang: z.string(),
            framework: z.string(),
            version: z.string(),
        }),
    }),
    product_profile: z.string().min(1, "product_profile 不能为空"),
    repos: z.array(RepoConfigSchema),
    build_commands: z.object({
        backend: BuildCommandsSchema,
        frontend: BuildCommandsSchema,
    }),
    scope: z.object({
        backend: z.array(z.string()),
        frontend: z.array(z.string()),
    }),
    conventions_file: z.string().optional(),
    verify_timeout: z.object({
        build: z.number().positive(),
        test: z.number().positive(),
    }).optional(),
    product_mapping: z.record(z.string(), z.string()).optional(),
    scaffold: z.object({
        base_package: z.string().optional(),
        templates: z.string().optional(),
        custom_templates_dir: z.string().optional(),
        contracts: z.record(z.string(), z.object({
            required_template_vars: z.array(z.string()).optional(),
            tech_stack: z.array(z.string()).optional(),
            output_boundary: z.object({
                forbidden_patterns: z.array(z.string()).optional(),
                max_file_size: z.number().positive().optional(),
            }).optional(),
        })).optional(),
    }).optional(),
});
/**
 * 验证原始配置数据。
 * @description 使用 Zod Schema 进行严格校验。支持旧格式（project.name 嵌套）的兼容处理。
 *              校验失败时返回部分解析结果（用默认值填充缺失字段）以及错误列表。
 * @param raw - 原始配置数据（通常为 YAML 解析后的对象）
 * @returns 包含验证后的配置对象和错误列表的结果
 */
export function validateConfig(raw) {
    debug("配置加载", "验证配置");
    // 先处理旧格式的 project.name 嵌套（深拷贝后再修改，避免 mutate 调用方传入的对象）
    let config = raw;
    if (raw && typeof raw === "object" && !("name" in raw) && "project" in raw) {
        config = structuredClone(raw);
        const legacy = config;
        const legacyProject = legacy["project"];
        if (legacyProject && typeof legacyProject === "object") {
            const proj = legacyProject;
            if (proj["name"])
                legacy["name"] = proj["name"];
            if (proj["product_profile"])
                legacy["product_profile"] = proj["product_profile"];
        }
    }
    const result = ProjectConfigSchema.safeParse(config);
    if (result.success) {
        return { config: result.data, errors: [] };
    }
    const errors = result.error.issues.map((issue) => ({
        path: issue.path.join("."),
        message: issue.message,
    }));
    // 尝试返回部分解析结果（带默认值填充缺失字段）
    const partial = fillDefaults(config);
    return { config: partial, errors };
}
/**
 * 用默认值填充缺失字段（内部方法）。
 * @description 当配置校验失败时，基于原始数据生成一个可用的部分配置对象。
 *              所有必填字段都有合理的空值默认值。
 * @param raw - 原始配置数据
 * @returns 填充默认值后的项目配置
 */
function fillDefaults(raw) {
    const r = raw || {};
    return {
        name: r.name || DEFAULT_PROJECT_NAME,
        tech_stack: {
            backend: { lang: r.tech_stack?.backend?.lang ?? "", framework: r.tech_stack?.backend?.framework ?? "", version: r.tech_stack?.backend?.version ?? "" },
            frontend: { lang: r.tech_stack?.frontend?.lang ?? "", framework: r.tech_stack?.frontend?.framework ?? "", version: r.tech_stack?.frontend?.version ?? "" },
        },
        product_profile: r.product_profile || "default",
        repos: Array.isArray(r.repos) ? r.repos : [],
        build_commands: {
            backend: { build: r.build_commands?.backend?.build ?? "", test: r.build_commands?.backend?.test ?? "", full: r.build_commands?.backend?.full ?? "" },
            frontend: { build: r.build_commands?.frontend?.build ?? "", test: r.build_commands?.frontend?.test ?? "", full: r.build_commands?.frontend?.full ?? "" },
        },
        scope: {
            backend: Array.isArray(r.scope?.backend) ? r.scope.backend : [],
            frontend: Array.isArray(r.scope?.frontend) ? r.scope.frontend : [],
        },
        conventions_file: r.conventions_file,
        verify_timeout: r.verify_timeout,
        product_mapping: r.product_mapping,
        scaffold: r.scaffold,
    };
}
// ── 配置加载 ──
/**
 * 加载项目配置文件。
 * @description 从 .soloforge/config.yaml 读取 YAML 配置，执行校验。
 *              若未显式设置 product_profile，会根据 product_mapping 和项目路径自动检测。
 *              配置文件不存在或解析失败时，使用默认空配置并输出警告。
 * @param projectPath - 项目根目录路径
 * @returns 加载并校验后的项目配置对象
 */
export async function loadProjectConfig(projectPath) {
    const configPath = path.join(projectPath, ".soloforge", "config.yaml");
    try {
        // 文件大小检查: 超过 1MB 拒绝加载，防止异常文件耗尽内存
        const stat = await fs.stat(configPath);
        const MAX_CONFIG_SIZE = 1024 * 1024; // 1MB
        if (stat.size > MAX_CONFIG_SIZE) {
            userWarn(`⚠️  SoloForge 配置文件过大 (${(stat.size / 1024).toFixed(0)}KB)，最大支持 1MB。`);
            return {
                name: path.basename(projectPath),
                _projectPath: projectPath,
                tech_stack: {
                    backend: { lang: "", framework: "", version: "" },
                    frontend: { lang: "", framework: "", version: "" },
                },
                product_profile: "default",
                repos: [],
                build_commands: {
                    backend: { build: "", test: "", full: "" },
                    frontend: { build: "", test: "", full: "" },
                },
                scope: { backend: [], frontend: [] },
            };
        }
        const content = await fs.readFile(configPath, "utf-8");
        let parsed = YAML.parse(content);
        parsed = migrateConfig(parsed);
        parsed._projectPath = projectPath;
        // 若未显式设置，从 product_mapping 自动检测 product_profile
        if (!parsed.product_profile && parsed.product_mapping) {
            parsed.product_profile = detectProductFromPath(projectPath, parsed.product_mapping) || "default";
        }
        // 校验
        const { config, errors } = validateConfig(parsed);
        if (errors.length > 0) {
            userWarn(`⚠️  SoloForge 配置校验警告 (${configPath}):`);
            for (const err of errors) {
                userWarn(`   - ${err.path || "(root)"}: ${err.message}`);
            }
            userWarn("   系统将以默认值填充缺失字段，部分功能可能不可用。");
        }
        debug("配置加载", `配置文件已加载 — ${configPath}`);
        config._projectPath = projectPath;
        return config;
    }
    catch {
        userWarn(`⚠️  SoloForge 配置文件不存在或无法解析: ${configPath}`);
        userWarn("   使用默认空配置。请运行 soloforge init 生成配置。");
        return {
            name: path.basename(projectPath),
            _projectPath: projectPath,
            tech_stack: {
                backend: { lang: "", framework: "", version: "" },
                frontend: { lang: "", framework: "", version: "" },
            },
            product_profile: "default",
            repos: [],
            build_commands: {
                backend: { build: "", test: "", full: "" },
                frontend: { build: "", test: "", full: "" },
            },
            scope: { backend: [], frontend: [] },
        };
    }
}
// ── 配置解析（支持无 config.yaml 的零配置回退） ──
/**
 * 将配置草稿转换为项目配置（内部方法）。
 * @param draft - 自动生成的配置草稿
 * @param projectPath - 项目根目录路径
 * @returns 转换后的项目配置
 */
function configDraftToProjectConfig(draft, projectPath) {
    return {
        schema_version: 1,
        name: draft.name,
        tech_stack: draft.tech_stack,
        product_profile: draft.product_profile,
        repos: [],
        build_commands: draft.build_commands,
        scope: draft.scope,
        _projectPath: projectPath,
    };
}
/**
 * 解析项目配置，支持无 config.yaml 的零配置回退。
 * @description 优先读取 .soloforge/config.yaml；若不存在，则通过指纹探测自动推断配置。
 * @param projectPath - 项目根目录路径
 * @returns 包含配置对象和来源信息的结果
 */
export async function resolveProjectConfig(projectPath, options = {}) {
    const configPath = path.join(projectPath, ".soloforge", "config.yaml");
    const configExists = await fs.access(configPath).then(() => true).catch(() => false);
    if (configExists) {
        debug("配置加载", "从 config.yaml 加载配置。");
        const config = await loadProjectConfig(projectPath);
        return { config, source: "config_file" };
    }
    debug("配置加载", "config.yaml 不存在，启用零配置回退。");
    const fingerprint = await detectFingerprint(projectPath);
    const draft = generateConfigDraft(fingerprint);
    const config = configDraftToProjectConfig(draft, projectPath);
    if (!options.quiet && !_inferredConfigWarningShown) {
        _inferredConfigWarningShown = true;
        userWarn(`ℹ️  SoloForge: config.yaml 未找到，使用自动推断配置（${draft.confidence} 置信度）。`);
        userWarn(`   运行 'soloforge init --auto' 持久化此配置。`);
    }
    return { config, source: "inferred" };
}
/**
 * 根据项目路径和映射表检测产品类型（内部方法）。
 * @description 遍历 product_mapping 中的路径模式（支持 * 和 ? 通配符），
 *              匹配项目路径以确定产品类型。
 * @param projectPath - 项目根目录路径
 * @param mapping - 路径模式到产品名的映射表
 * @returns 匹配到的产品名称，未匹配则返回 null
 */
function detectProductFromPath(projectPath, mapping) {
    for (const [pattern, product] of Object.entries(mapping)) {
        const escaped = pattern.replace(/[.+^${}()|[\]\\]/g, "\\$&");
        const regex = new RegExp("^" + escaped.replace(/\*/g, ".*").replace(/\?/g, ".") + "$");
        if (regex.test(projectPath)) {
            return product;
        }
    }
    return null;
}
/**
 * 获取项目知识目录路径。
 * @description 返回 {projectPath}/.soloforge/knowledge 目录的绝对路径。
 * @param config - 项目配置对象
 * @returns 项目知识目录的绝对路径
 */
export function getProjectKnowledgeDir(config) {
    debug("配置加载", "获取项目知识目录");
    const base = config._projectPath || process.cwd();
    return path.join(base, ".soloforge", "knowledge");
}
/**
 * 获取全局模式目录路径。
 * @description 返回 ~/.soloforge/patterns 目录路径，存放跨项目共享的知识模式。
 * @returns 全局模式目录的绝对路径
 */
export function getGlobalPatternsDir() {
    debug("配置加载", "获取全局模式目录");
    return path.join(os.homedir(), ".soloforge", "patterns");
}
//# sourceMappingURL=loader.js.map