/**
 * 长任务 Job 管理 — checkpoint、heartbeat、resume/cancel、认知锚点。
 * advisory: 不 hard block，提供恢复和监控能力。
 *
 * 职责:
 * - 创建/更新/查询 job checkpoint
 * - heartbeat 检测活跃状态
 * - 从 checkpoint 恢复任务
 * - 认知锚点（上下文摘要）
 */
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";
export class JobManager {
    stateDir;
    constructor(stateDir) {
        this.stateDir = stateDir;
        fss.mkdirSync(stateDir, { recursive: true });
    }
    /**
     * 创建新 job，分配 job_id 并写入初始 checkpoint。
     *
     * @param taskId - 任务 ID
     * @param phase - 当前阶段
     * @param initialSummary - 初始摘要
     * @returns 初始 job checkpoint
     */
    async createJob(taskId, phase, initialSummary) {
        const jobId = `job-${crypto.randomUUID().slice(0, 8)}`;
        const now = new Date().toISOString();
        const checkpoint = {
            job_id: jobId,
            task_id: taskId,
            phase,
            heartbeat_at: now,
            checkpoint_number: 1,
            cognitive_anchor: {
                phase,
                summary: initialSummary,
                key_decisions: [],
                pending_actions: [],
                created_at: now,
            },
            state_snapshot: {},
        };
        await this.writeCheckpoint(checkpoint);
        return checkpoint;
        debug("任务管理", "创建 job", jobId, "任务:", taskId, "阶段:", phase);
    }
    /**
     * 更新 checkpoint — 记录心跳和阶段进展。
     *
     * @param jobId - Job ID
     * @param taskId - 任务 ID
     * @param phase - 当前阶段
     * @param anchor - 认知锚点更新内容
     * @param stateSnapshot - 可选的状态快照
     * @returns 更新后的 checkpoint，未找到则返回 null
     */
    async updateCheckpoint(jobId, taskId, phase, anchor, stateSnapshot) {
        const existing = await this.loadLatestCheckpoint(jobId, taskId);
        if (!existing)
            return null;
        const now = new Date().toISOString();
        const checkpoint = {
            job_id: jobId,
            task_id: taskId,
            phase,
            heartbeat_at: now,
            checkpoint_number: existing.checkpoint_number + 1,
            cognitive_anchor: {
                phase,
                summary: anchor.summary ?? existing.cognitive_anchor.summary,
                key_decisions: anchor.key_decisions ?? existing.cognitive_anchor.key_decisions,
                pending_actions: anchor.pending_actions ?? existing.cognitive_anchor.pending_actions,
                created_at: now,
            },
            state_snapshot: stateSnapshot ?? existing.state_snapshot,
        };
        await this.writeCheckpoint(checkpoint);
        return checkpoint;
    }
    /**
     * 心跳 — 只更新 heartbeat_at 时间戳。
     *
     * @param jobId - Job ID
     * @param taskId - 任务 ID
     * @returns 是否成功更新心跳
     */
    async heartbeat(jobId, taskId) {
        const existing = await this.loadLatestCheckpoint(jobId, taskId);
        if (!existing)
            return false;
        existing.heartbeat_at = new Date().toISOString();
        await this.writeCheckpoint(existing);
        return true;
    }
    /**
     * 查询 job 状态 — 包括是否活跃、距上次心跳时间。
     *
     * @param jobId - Job ID
     * @param taskId - 任务 ID
     * @param maxAgeMs - 最大存活时间（毫秒），默认 5 分钟
     * @returns Job 状态，未找到则返回 null
     */
    async getJobStatus(jobId, taskId, maxAgeMs = 5 * 60 * 1000) {
        const checkpoint = await this.loadLatestCheckpoint(jobId, taskId);
        if (!checkpoint)
            return null;
        const lastHeartbeat = new Date(checkpoint.heartbeat_at).getTime();
        const elapsed = Date.now() - lastHeartbeat;
        return {
            job_id: jobId,
            task_id: taskId,
            is_alive: elapsed < maxAgeMs,
            last_heartbeat_ms_ago: elapsed,
            current_phase: checkpoint.phase,
            checkpoint_count: checkpoint.checkpoint_number,
            latest_anchor: checkpoint.cognitive_anchor,
        };
    }
    /**
     * 从最新 checkpoint 恢复 — 返回 checkpoint 数据供调用方恢复状态。
     *
     * @param jobId - Job ID
     * @param taskId - 任务 ID
     * @returns 最新的 checkpoint，未找到则返回 null
     */
    async resumeFromCheckpoint(jobId, taskId) {
        const cp = await this.loadLatestCheckpoint(jobId, taskId);
        if (cp) {
            debug("任务管理", "从 checkpoint 恢复 job", jobId, "阶段:", cp.phase);
        }
        else {
            debug("任务管理", "未找到 job", jobId, "的 checkpoint");
        }
        return cp;
    }
    /**
     * 取消 job — 删除所有 checkpoint 文件。
     *
     * @param jobId - Job ID
     * @param taskId - 任务 ID
     * @returns 是否成功删除
     */
    async cancelJob(jobId, taskId) {
        const pattern = `${jobId}`;
        const dir = path.join(this.stateDir, "jobs");
        if (!fss.existsSync(dir))
            return false;
        const files = await fs.readdir(dir);
        let removed = false;
        debug("任务管理", "取消 job", jobId, "任务:", taskId);
        for (const f of files) {
            if (f.includes(pattern) && f.endsWith(".json")) {
                await fs.unlink(path.join(dir, f));
                removed = true;
            }
        }
        return removed;
    }
    /**
     * 列出所有活跃 job。
     *
     * @param maxAgeMs - 最大存活时间（毫秒），默认 5 分钟
     * @returns 活跃的 job 状态列表
     */
    async listActiveJobs(maxAgeMs = 5 * 60 * 1000) {
        const dir = path.join(this.stateDir, "jobs");
        if (!fss.existsSync(dir))
            return [];
        const files = await fs.readdir(dir);
        const checkpoints = [];
        for (const f of files.filter((f) => f.endsWith(".json"))) {
            try {
                const data = await fs.readFile(path.join(dir, f), "utf-8");
                const parsed = safeJsonParse(data);
                if (parsed)
                    checkpoints.push(parsed);
            }
            catch (e) {
                debug("作业管理器", "读取 checkpoint 文件失败(预期行为: 文件损坏) —", f, e);
            }
        }
        // 按 job_id 分组，取每个 job 的最新 checkpoint
        const latest = new Map();
        for (const cp of checkpoints) {
            const existing = latest.get(cp.job_id);
            if (!existing || cp.checkpoint_number > existing.checkpoint_number) {
                latest.set(cp.job_id, cp);
            }
        }
        const statuses = [];
        for (const cp of latest.values()) {
            const elapsed = Date.now() - new Date(cp.heartbeat_at).getTime();
            if (elapsed >= maxAgeMs)
                continue;
            statuses.push({
                job_id: cp.job_id,
                task_id: cp.task_id,
                is_alive: true,
                last_heartbeat_ms_ago: elapsed,
                current_phase: cp.phase,
                checkpoint_count: cp.checkpoint_number,
                latest_anchor: cp.cognitive_anchor,
            });
        }
        return statuses;
    }
    /**
     * 仅凭 job_id 反查 task_id — 扫描 checkpoint 文件，返回最新一条的 task_id。
     * 用于 sf_status 在不传 task_id 时自动推导。
     *
     * @param jobId - Job ID
     * @returns 对应的 task_id，未找到则返回 null
     */
    async resolveTaskId(jobId) {
        const dir = this.getJobsDir();
        const files = await fs.readdir(dir);
        const matching = files
            .filter((f) => f.startsWith(jobId) && f.endsWith(".json"))
            .sort()
            .reverse();
        for (const f of matching) {
            try {
                const raw = safeJsonParse(await fs.readFile(path.join(dir, f), "utf-8"));
                // 运行时验证关键字段
                if (!raw || typeof raw !== "object" || typeof raw.task_id !== "string") {
                    debug("作业管理器", "checkpoint 格式无效(缺少 task_id) —", f);
                    continue;
                }
                return raw.task_id;
            }
            catch (e) {
                debug("作业管理器", "读取 checkpoint 文件失败(预期行为: 文件损坏) —", f, e);
            }
        }
        return null;
    }
    // ── 内部方法 ──
    getJobsDir() {
        const dir = path.join(this.stateDir, "jobs");
        fss.mkdirSync(dir, { recursive: true });
        return dir;
    }
    async writeCheckpoint(checkpoint) {
        const dir = this.getJobsDir();
        const filename = `${checkpoint.job_id}-${checkpoint.checkpoint_number.toString().padStart(4, "0")}.json`;
        const filePath = path.join(dir, filename);
        const tmpPath = `${filePath}.tmp`;
        await fs.writeFile(tmpPath, JSON.stringify(checkpoint, null, 2));
        await fs.rename(tmpPath, filePath);
    }
    async loadLatestCheckpoint(jobId, taskId) {
        const dir = this.getJobsDir();
        const files = await fs.readdir(dir);
        const matching = files
            .filter((f) => f.startsWith(jobId) && f.endsWith(".json"))
            .sort()
            .reverse();
        for (const f of matching) {
            try {
                const data = await fs.readFile(path.join(dir, f), "utf-8");
                const raw = safeJsonParse(data);
                // 运行时验证关键字段
                if (!raw || typeof raw.task_id !== "string") {
                    debug("作业管理器", "checkpoint 格式无效(缺少 task_id) —", f);
                    continue;
                }
                const cp = raw;
                if (cp.task_id === taskId)
                    return cp;
            }
            catch (e) {
                debug("作业管理器", "读取 checkpoint 文件失败(预期行为: 文件损坏) —", f, e);
            }
        }
        return null;
    }
}
//# sourceMappingURL=job_manager.js.map