/**
 * ============================================================
 *  src/context/microCompactLite.ts —— 工具结果微压缩（v2）
 * ------------------------------------------------------------
 *  对齐 kakadeai 的 microCompact 设计，纯规则实现，零 LLM 调用。
 *
 *  核心思路：
 *    工具执行结果在写入 history 前经过一道"单向检查门"，
 *    用规则压缩冗余内容，减少 token 消耗但不丢失关键信息。
 *
 *  对齐 kakadeai 的两条有效路径：
 *    Path A: Cached MC → 本方案用 SHA-1 进程缓存 + 计数淘汰实现
 *    Path B: Time-based MC → 本方案用轮次衰减实现
 *
 *  kakadeai 已删除 Legacy LLM path → 本方案不实现 LLM 压缩
 * ============================================================
 */
import { createHash } from 'node:crypto';
// ==================== 配置常量 ====================
/** 同一内容最多原样保留多少次，之后用压缩引用替代 */
const MAX_REPEAT_COUNT = 3;
/** 触发截断的字符数阈值（仅白名单内的工具） */
const MAX_RESULT_SIZE = 4000;
/** 截断时保留的头部字符数 */
const HEAD_KEEP_CHARS = 2000;
/** 截断时保留的尾部字符数 */
const TAIL_KEEP_CHARS = 1000;
/** 时间衰减：条目首次出现后超过多少轮自动清理 */
const MAX_KEEP_ROUNDS = 10;
/** 短内容阈值：不超过此长度的内容直接放行（不做任何处理） */
const SHORT_CONTENT_THRESHOLD = 200;
export function createMicroCompactState() {
    return { turn: 0, cache: new Map() };
}
const defaultState = createMicroCompactState();
export function incrementTurn(state = defaultState) {
    state.turn++;
}
export function resetTurn(state = defaultState) {
    state.turn = 0;
}
/** 获取当前缓存统计 */
export function getCacheStats(state = defaultState) {
    let compressedCount = 0;
    let expiredCount = 0;
    let truncatedCount = 0;
    let totalCharsSaved = 0;
    let repeatedCount = 0;
    for (const entry of state.cache.values()) {
        if (entry.count > 1)
            repeatedCount++;
        if (entry.count > MAX_REPEAT_COUNT) {
            compressedCount++;
            totalCharsSaved += (entry.count - MAX_REPEAT_COUNT) * 500;
        }
        if (state.turn - entry.firstSeenTurn > MAX_KEEP_ROUNDS) {
            expiredCount++;
        }
    }
    return { totalEntries: state.cache.size, repeatedCount, compressedCount, expiredCount, truncatedCount, totalCharsSaved };
}
/** 清空缓存（供 /new 命令或测试使用） */
export function clearCache(state = defaultState) {
    state.cache.clear();
    state.turn = 0;
}
// ==================== 工具白名单 ====================
/**
 * 只有这些工具的返回值参与微压缩（长度截断 + SHA-1 去重）。
 *
 * 排除 Read/Edit/Write 的原因：输出本身就短，有独立的大小限制。
 * 排除 WebSearch 的原因：搜索结果是"一次性知识注入"，首次必须完整展示给模型；
 *   后续重复时由 SHA-1 去重处理，但不做长度截断（避免丢失关键信息）。
 */
const COMPRESSIBLE_TOOLS = new Set([
    'Grep',
    'Bash',
    'WebFetch',
    'Glob',
]);
// ==================== 核心函数 ====================
/**
 * 对一条工具执行结果进行微压缩处理。
 *
 * 这是整个模块唯一的对外接口。loop.ts 在把 tool_result 塞入 history 之前调用它。
 *
 * 处理流水线（按优先级）：
 *   Rule 0: 错误/短内容 → 原样放行
 *   Rule 1: SHA-1 去重 → 相同内容第 4 次起压缩为引用标记
 *   Rule 2: 时间衰减 → 超过 N 轮未再出现的内容清理掉
 *   Rule 3: 长度截断 → 白名单工具超长内容保留头尾 + 省略标记
 *
 * @param toolName 工具名
 * @param content 工具返回的原始内容
 * @returns 处理后的内容（可能被压缩、截断、或原样返回）
 */
export function microCompact(toolName, content, state = defaultState) {
    if (!content)
        return content;
    // Rule 0: 错误信息原样保留
    if (content.startsWith('Error:') || content.startsWith('错误')) {
        return content;
    }
    // Rule 0b: 短内容直接放行
    if (content.length <= SHORT_CONTENT_THRESHOLD) {
        return content;
    }
    // Rule 1: SHA-1 去重
    const hash = sha1(content);
    const existing = state.cache.get(hash);
    if (existing) {
        existing.count++;
        if (existing.count > MAX_REPEAT_COUNT) {
            return `[↑ ${existing.firstToolName} 结果已重复出现 ${existing.count} 次（相同内容已省略）]`;
        }
        return content;
    }
    // 首次看到此内容
    state.cache.set(hash, { count: 1, firstToolName: toolName, firstSeenTurn: state.turn });
    // Rule 2: 不需要——首次出现不存在时间衰减问题
    // Rule 3: 长度截断（仅白名单工具）
    if (content.length > MAX_RESULT_SIZE && COMPRESSIBLE_TOOLS.has(toolName)) {
        return truncateContent(content);
    }
    return content;
}
/**
 * 清理过期条目（每轮开始前调用）。
 * 将超过 MAX_KEEP_ROUNDS 未被再次访问的条目标记为过期，
 * 后续如果这些条目的 hash 再次命中，会作为"新内容"重新计数。
 */
export function expireOldEntries(state = defaultState) {
    let expired = 0;
    for (const [hash, entry] of state.cache) {
        if (state.turn - entry.firstSeenTurn > MAX_KEEP_ROUNDS) {
            state.cache.delete(hash);
            expired++;
        }
    }
    return expired;
}
// ==================== 内部函数 ====================
function sha1(str) {
    return createHash('sha1').update(str).digest('hex');
}
function truncateContent(content) {
    const omitted = content.length - HEAD_KEEP_CHARS - TAIL_KEEP_CHARS;
    return (content.slice(0, HEAD_KEEP_CHARS) +
        `\n\n[... 省略了 ${omitted.toLocaleString()} 字符 ...]\n\n` +
        content.slice(-TAIL_KEEP_CHARS));
}
