import fs from "node:fs/promises";
import fss from "node:fs";
import path from "node:path";
import crypto from "node:crypto";
import { debug } from "../core/logger.js";
import { safeJsonParse } from "../core/helpers.js";
/**
 * 技术债务追踪模块 — 持久化存储、查询、统计和清理技术债务条目。
 * 基于文件系统实现（JSON 单文件存储），零外部依赖。
 * 每条债务对应一个 `.json` 文件，以 `debt-<short-uuid>.json` 命名。
 */
/** 已解决债务的默认保留天数，超过后将被 cleanup 自动清理 */
const MAX_DEBT_AGE_DAYS = 30;
/**
 * 技术债务追踪器 — 负责债务的增删改查、统计聚合与过期清理。
 * 债务数据存储在 `{projectPath}/.soloforge/state/debt/` 目录下。
 */
export class DebtTracker {
    debtDir;
    /**
     * 初始化债务追踪器。
     * @param projectPath - 项目根目录路径
     */
    constructor(projectPath) {
        this.debtDir = path.join(projectPath, ".soloforge", "state", "debt");
    }
    /** 确保债务存储目录存在，不存在则递归创建 */
    async ensureDir() {
        await fs.mkdir(this.debtDir, { recursive: true });
    }
    /**
     * 将代码审查发现记录为一条新的技术债务。
     * 使用原子写入（先写临时文件再 rename）保证数据一致性。
     * @param finding - 代码审查发现结果
     * @param taskId - 关联的任务 ID
     * @returns 新创建的债务条目 ID
     */
    async recordDebt(finding, taskId) {
        await this.ensureDir();
        const id = `debt-${crypto.randomUUID().slice(0, 8)}`;
        // 审查维度到债务类别的映射表
        const dimensionToCategory = {
            quality: "other",
            security: "security",
            performance: "performance",
            debt: "other",
            architecture: "other",
        };
        const item = {
            id,
            task_id: taskId,
            file: finding.file,
            line: finding.line,
            title: finding.title,
            description: finding.description,
            severity: finding.severity,
            category: dimensionToCategory[finding.dimension] ?? "other",
            discovered_at: new Date().toISOString(),
            status: "open",
        };
        const filePath = path.join(this.debtDir, `${id}.json`);
        const tmpPath = `${filePath}.tmp`;
        // 原子写入：先写入临时文件，再重命名为目标文件，防止写入中断导致数据损坏
        await fs.writeFile(tmpPath, JSON.stringify(item, null, 2));
        await fs.rename(tmpPath, filePath);
        debug("债务追踪", `记录新债务 ${id} [${item.category}] ${item.title}`);
        return id;
    }
    /**
     * 列出所有状态为 open 的债务条目，按发现时间升序排列。
     * @returns 未解决的债务条目列表
     */
    async listOpen() {
        await this.ensureDir();
        const files = await fs.readdir(this.debtDir);
        const items = [];
        for (const file of files) {
            if (!file.endsWith(".json"))
                continue;
            try {
                const content = await fs.readFile(path.join(this.debtDir, file), "utf-8");
                const item = safeJsonParse(content);
                if (item && item.status === "open")
                    items.push(item);
            }
            catch (e) {
                debug("债务追踪", "读取文件错误 —", e);
            }
        }
        // 按发现时间升序排列，最早的债务排在前面
        return items.sort((a, b) => a.discovered_at.localeCompare(b.discovered_at));
    }
    /** 将指定债务标记为「已确认」（acknowledged）。
     * @param debtId - 债务条目 ID
     */
    async acknowledge(debtId) {
        await this.updateStatus(debtId, "acknowledged");
        debug("债务追踪", `债务 ${debtId} 已标记为「已确认」`);
    }
    /** 将指定债务标记为「已解决」（resolved），同时记录解决时间。
     * @param debtId - 债务条目 ID
     */
    async resolve(debtId) {
        await this.updateStatus(debtId, "resolved", new Date().toISOString());
        debug("债务追踪", `债务 ${debtId} 已标记为「已解决」`);
    }
    /**
     * 按债务类别统计未解决的条目数量。
     * @returns 以类别名为键、数量为值的统计对象
     */
    async getStats() {
        const openItems = await this.listOpen();
        const stats = {};
        for (const item of openItems) {
            const cat = item.category ?? "other";
            stats[cat] = (stats[cat] ?? 0) + 1;
        }
        return stats;
    }
    /**
     * 计算指定天数内债务的增减趋势。
     * @param sinceDays - 统计回溯天数
     * @returns 包含新增数（created）、已解决数（resolved）和净增量（net）的趋势数据
     */
    async getTrend(sinceDays) {
        await this.ensureDir();
        const cutoff = new Date();
        cutoff.setDate(cutoff.getDate() - sinceDays);
        const cutoffStr = cutoff.toISOString();
        let created = 0;
        let resolved = 0;
        const files = await fs.readdir(this.debtDir);
        for (const file of files) {
            if (!file.endsWith(".json"))
                continue;
            try {
                const content = await fs.readFile(path.join(this.debtDir, file), "utf-8");
                const item = safeJsonParse(content);
                if (!item)
                    continue;
                if (item.discovered_at >= cutoffStr)
                    created++;
                // 仅统计在时间窗口内解决的条目
                if (item.status === "resolved" && item.resolved_at && item.resolved_at >= cutoffStr)
                    resolved++;
            }
            catch (e) {
                debug("债务追踪", "读取文件错误 —", e);
            }
        }
        // 净增量 = 新增 - 已解决；正值表示债务在增长
        return { created, resolved, net: created - resolved };
    }
    /**
     * 按优先级对未解决债务排序。优先级 = 严重程度分 x 影响程度分。
     * severity: critical=4, warning=3, info=1
     * impact: high=3, medium=2, low=1
     * @returns 按优先级降序排列的债务列表（最高优先在前）
     */
    async prioritize() {
        const openItems = await this.listOpen();
        // 严重程度权重：critical 最重，info 最轻
        const sevScore = { critical: 4, warning: 3, info: 1 };
        // 影响程度权重：high 最重，low 最轻
        const impScore = { high: 3, medium: 2, low: 1 };
        return [...openItems].sort((a, b) => {
            const sa = (sevScore[a.severity] ?? 1) * (impScore[a.impact ?? "medium"] ?? 2);
            const sb = (sevScore[b.severity] ?? 1) * (impScore[b.impact ?? "medium"] ?? 2);
            return sb - sa;
        });
    }
    /**
     * 清理已过期且已解决的债务条目。
     * 超过 MAX_DEBT_AGE_DAYS（30 天）的已解决条目将被删除。
     * @returns 本次清理删除的条目数量
     */
    async cleanup() {
        await this.ensureDir();
        const files = await fs.readdir(this.debtDir);
        const cutoff = new Date();
        cutoff.setDate(cutoff.getDate() - MAX_DEBT_AGE_DAYS);
        let removed = 0;
        for (const file of files) {
            if (!file.endsWith(".json"))
                continue;
            try {
                const filePath = path.join(this.debtDir, file);
                const content = await fs.readFile(filePath, "utf-8");
                const item = safeJsonParse(content);
                if (!item)
                    continue;
                if (item.status === "resolved" &&
                    item.resolved_at &&
                    new Date(item.resolved_at) < cutoff) {
                    await fs.unlink(filePath);
                    removed++;
                }
            }
            catch (e) {
                debug("债务追踪", "读取文件错误 —", e);
            }
        }
        if (removed > 0) {
            debug("债务追踪", `清理完成，删除 ${removed} 条过期已解决债务`);
        }
        return removed;
    }
    /**
     * 更新指定债务条目的状态。使用原子写入保证数据一致性。
     * @param debtId - 债务条目 ID
     * @param status - 新状态（acknowledged 或 resolved）
     * @param resolvedAt - 解决时间（仅当 status 为 resolved 时有效）
     */
    async updateStatus(debtId, status, resolvedAt) {
        const filePath = path.join(this.debtDir, `${debtId}.json`);
        // 债务文件不存在则静默返回，避免重复操作的报错
        if (!fss.existsSync(filePath))
            return;
        const content = await fs.readFile(filePath, "utf-8");
        const item = safeJsonParse(content);
        if (!item)
            return;
        item.status = status;
        if (resolvedAt)
            item.resolved_at = resolvedAt;
        const tmpPath = `${filePath}.tmp`;
        await fs.writeFile(tmpPath, JSON.stringify(item, null, 2));
        await fs.rename(tmpPath, filePath);
    }
}
//# sourceMappingURL=debt_tracker.js.map