/**
 * Scope Lease — 追踪任务对文件的 advisory 租约。
 *
 * CAP-011 实验性: advisory only, 不 hard block, 不阻止写入。
 * 职责: 记录哪个任务正在操作哪些文件，供其他任务查询冲突。
 *
 * 生命周期:
 * - 任务进入 executing 时 acquire
 * - 任务完成/失败/取消时 release
 * - 其他任务写前可 queryLease 查询 advisory 冲突
 */
import fs from "node:fs";
import path from "node:path";
import { debug } from "../core/logger.js";
import { safeJsonParse } from "../core/helpers.js";
import { isPathInScope } from "../core/path_scope_utils.js";
/** 范围租约存储 */
export class ScopeLeaseStore {
    filePath;
    leases = new Map();
    loaded = false;
    /**
     * 创建范围租约存储实例。
     * @param stateDir - 状态存储目录路径
     */
    constructor(stateDir) {
        this.filePath = path.join(stateDir, "scope-leases.json");
        if (!fs.existsSync(stateDir)) {
            // 无操作
        }
    }
    /** 从磁盘加载租约数据（首次访问时自动调用） */
    ensureLoaded() {
        if (this.loaded)
            return;
        this.loaded = true;
        try {
            const data = fs.readFileSync(this.filePath, "utf-8");
            const entries = safeJsonParse(data) ?? [];
            for (const e of entries) {
                if (e.released_at === null) {
                    this.leases.set(e.task_id, e);
                }
            }
        }
        catch {
            this.leases = new Map();
        }
    }
    /** 将租约数据持久化到磁盘 */
    persist() {
        const entries = [...this.leases.values()];
        const tmpPath = `${this.filePath}.tmp`;
        fs.writeFileSync(tmpPath, JSON.stringify(entries, null, 2));
        fs.renameSync(tmpPath, this.filePath);
    }
    /**
     * 为任务获取文件范围租约。
     * @param taskId - 任务 ID
     * @param filePaths - 任务操作的文件路径列表
     * @returns 创建的租约条目
     */
    acquire(taskId, filePaths) {
        debug("ScopeLease", "获取租约", taskId, "文件数:", filePaths.length);
        this.ensureLoaded();
        const resolved = filePaths.map(f => path.resolve(f));
        const entry = {
            task_id: taskId,
            file_paths: resolved,
            acquired_at: new Date().toISOString(),
            released_at: null,
        };
        this.leases.set(taskId, entry);
        this.persist();
        return entry;
    }
    /**
     * 释放任务的文件范围租约。
     * 释放前检查是否有其他活跃任务持有重叠路径。
     * @param taskId - 任务 ID
     * @returns 释放结果，包含是否成功和冲突警告
     */
    release(taskId) {
        debug("ScopeLease", "释放租约", taskId);
        this.ensureLoaded();
        const entry = this.leases.get(taskId);
        if (!entry || entry.released_at) {
            return { released: false, warnings: [] };
        }
        // 检查是否有其他活跃任务依赖相同文件路径
        const warnings = [];
        for (const [otherId, other] of this.leases) {
            if (otherId === taskId || other.released_at)
                continue;
            const overlap = entry.file_paths.some(p => other.file_paths.some(op => op === p || op.startsWith(p + "/") || p.startsWith(op + "/")));
            if (overlap) {
                warnings.push(`任务 ${otherId} 的 scope 与本任务有重叠，释放后可能出现写入冲突`);
            }
        }
        entry.released_at = new Date().toISOString();
        this.leases.delete(taskId);
        this.persist();
        return { released: true, warnings };
    }
    /**
     * 查询指定文件是否存在与其他任务的租约冲突。
     * @param taskId - 当前任务 ID（排除自身）
     * @param filePath - 要查询的文件路径
     * @returns 租约冲突信息，无冲突时返回 null
     */
    queryLease(taskId, filePath) {
        this.ensureLoaded();
        const resolved = path.resolve(filePath);
        for (const [ownerId, entry] of this.leases) {
            if (ownerId === taskId)
                continue;
            for (const leased of entry.file_paths) {
                if (leased === resolved || isParentPath(leased, resolved)) {
                    return {
                        file_path: resolved,
                        leased_by: ownerId,
                        lease_acquired_at: entry.acquired_at,
                        severity: "advisory",
                        message: `advisory: file is in scope of task ${ownerId} (leased since ${entry.acquired_at})`,
                    };
                }
            }
        }
        return null;
    }
    /**
     * 获取所有活跃的租约列表。
     * @returns 活跃租约条目数组
     */
    getActiveLeases() {
        this.ensureLoaded();
        return [...this.leases.values()];
    }
    /**
     * 获取指定任务的租约信息。
     * @param taskId - 任务 ID
     * @returns 对应的租约条目，未找到时返回 null
     */
    getLeaseForTask(taskId) {
        this.ensureLoaded();
        return this.leases.get(taskId) ?? null;
    }
    /**
     * 获取租约摘要统计。
     * @returns 租约摘要信息
     */
    getSummary() {
        this.ensureLoaded();
        const leasedByTask = {};
        let totalFiles = 0;
        for (const [taskId, entry] of this.leases) {
            leasedByTask[taskId] = [...entry.file_paths];
            totalFiles += entry.file_paths.length;
        }
        return {
            active_leases: this.leases.size,
            total_files_leased: totalFiles,
            leased_by_task: leasedByTask,
        };
    }
    /**
     * 清除所有租约数据。
     */
    clear() {
        debug("ScopeLease", "清除所有租约");
        this.leases.clear();
        this.loaded = true;
        this.persist();
    }
}
/** 判断 child 路径是否在 parent 路径下 */
function isParentPath(parent, child) {
    return isPathInScope(child, parent, { caseSensitive: true });
}
//# sourceMappingURL=scope_lease.js.map