/**
 * ============================================================
 *  src/context/persistContext.ts —— 单上下文持久化（无会话管理）
 * ------------------------------------------------------------
 *  做的事：
 *    把"上一次关闭时的对话历史"落盘到一个固定文件。下次启动（TUI 或 -p）
 *    自动加载，等于无缝续上。整个项目就一个上下文，不存在 session id /
 *    list / resume 之类的概念。
 *
 *  文件路径：
 *    ~/.minimal-agent/sessions/<目录哈希>.json
 *    （按当前工作目录隔离；可用 MINIMAL_AGENT_CONTEXT_FILE 覆盖，给测试用）
 *
 *  存储格式：
 *    { updatedAt: number; messages: Message[] }
 *
 *  抉择：
 *    - load 出来若为空或损坏 → 当作"没历史"返回 null，调用方 fallback 到
 *      只含 system prompt 的 fresh history。永不抛错。
 *    - save 直接覆写整个文件，不做 atomic（教学项目可接受；进程崩溃丢的
 *      就是当前这一轮）。
 *    - 不做 system prompt 替换：调用方拿到 messages 后想替/不替自己决定。
 * ============================================================
 */
import { mkdir, readFile, readdir, rmdir, unlink, writeFile } from 'node:fs/promises';
import { dirname, join } from 'node:path';
import { getWorkingDir } from '../bootstrap/workingDir.js';
import { archiveCurrent } from './archive.js';
import { sessionFileFor } from './sessionPath.js';
/** 上下文文件路径（按工作目录隔离，可用环境变量覆盖） */
export function getContextPath() {
    if (process.env.MINIMAL_AGENT_CONTEXT_FILE) {
        return process.env.MINIMAL_AGENT_CONTEXT_FILE;
    }
    return sessionFileFor(getWorkingDir());
}
/**
 * 加载上次的上下文。
 *  - 文件不存在 / 解析失败 / 格式不符 → 返回 null（调用方应当 fallback）
 *  - 成功 → 返回 messages 数组（可能为空数组）
 *  - 显式传入 file 时优先使用，便于 HTTP 模式按 sessionKey 隔离
 */
export async function loadContext(file) {
    const target = file ?? getContextPath();
    try {
        const raw = await readFile(target, 'utf8');
        const data = JSON.parse(raw);
        if (!Array.isArray(data.messages))
            return null;
        return data.messages;
    }
    catch {
        return null;
    }
}
/**
 * 把 messages 数组过滤掉 _ephemeral=true 的 system 消息。
 *
 * v2.x 新增：UI 用 ephemeral system 消息把 slash 命令输出落进 scrollback
 * （替代 process.stdout.write 旁路 Ink）。这种消息只供 UI 展示，不该污染
 * 持久化历史；这里在 saveContext 之前做一次过滤。
 *
 * 导出供测试断言。
 */
export function filterEphemeralMessages(messages) {
    return messages.filter((m) => !(m.role === 'system' && m._ephemeral === true));
}
/**
 * 保存当前 messages 为"上下文文件"，覆盖之前内容。
 * 写失败不抛——丢一次落盘比把进程崩掉划算。
 *
 * v2.x 新增：写盘前过滤掉 _ephemeral=true 的 system 消息（UI 临时提示，
 * 不应进入持久化历史）。
 */
export async function saveContext(messages, file) {
    const target = file ?? getContextPath();
    try {
        await mkdir(dirname(target), { recursive: true });
        const persistable = filterEphemeralMessages(messages);
        const data = { updatedAt: Date.now(), messages: persistable };
        await writeFile(target, JSON.stringify(data), 'utf8');
    }
    catch {
        // 静默吞：持久化失败不该影响对话本身
    }
}
/**
 * 清空上下文文件（/new 用）。文件不存在也算成功。
 *
 * 同时清理当前工作目录下所有 `.minimal-agent` / `.minimal-agent-<tag>/`
 * 状态目录（各类插件可能在 cwd 下创建多个 sessionTag 目录）。
 * 注意：只扫当前 cwd 的子目录，不递归、不跨工作目录，保证并行 session 不串台。
 */
export async function clearContext(file) {
    const target = file ?? getContextPath();
    // 归档当前历史（仅含 system 的空对话会跳过）；失败不阻塞 clear
    // 显式传 file（HTTP 模式 / 测试覆盖路径）或 MINIMAL_AGENT_CONTEXT_FILE 激活时
    // 跳过 archive：这两种语义下 caller 自管路径，不应污染默认 archive 目录
    const skipArchive = file !== undefined || !!process.env.MINIMAL_AGENT_CONTEXT_FILE;
    if (!skipArchive) {
        try {
            const current = await loadContext(target);
            if (current && current.length > 0) {
                await archiveCurrent(current);
            }
        }
        catch (e) {
            process.stderr.write(`[minimal-agent] archive 失败（继续 clear）：${e.message}\n`);
        }
    }
    try {
        await unlink(target);
    }
    catch {
    }
    const cwd = getWorkingDir();
    let topEntries;
    try {
        topEntries = await readdir(cwd);
    }
    catch {
        return;
    }
    const stateDirs = topEntries.filter((name) => name === '.minimal-agent' || name.startsWith('.minimal-agent-'));
    for (const name of stateDirs) {
        const dir = join(cwd, name);
        try {
            const entries = await readdir(dir);
            for (const entry of entries) {
                try {
                    await unlink(join(dir, entry));
                }
                catch {
                }
            }
            await rmdir(dir);
        }
        catch {
        }
    }
}
