/**
 * @module        core/claudeJsonl
 * @description   解析 Claude CLI 自身写的 ACP session 历史 jsonl
 *
 * @layer         core
 *
 * Claude CLI 把每个 ACP session 的完整对话写在
 *   ~/.claude/projects/<encoded-cwd>/<sessionId>.jsonl
 * 一个 sessionId 一个 jsonl 文件（1:1）。
 *
 * 把它解析成 ChatMessage[] 后，Console 就能展示 `sps agent --chat` /
 * `sps agent --name foo` 创建的 CLI 会话历史，而不必双写到
 * ~/.coral/chat-sessions/.
 */
import { existsSync, readFileSync, statSync } from 'node:fs';
import { claudeJsonlFile } from '../shared/runtimePaths.js';
/** 文件是否存在（不解析内容） */
export function jsonlExists(cwd, sessionId) {
    return existsSync(claudeJsonlFile(cwd, sessionId));
}
/**
 * 读 jsonl 并转成 ChatMessage[]。
 * - 跳过 isMeta / system / progress / queue-operation / file-history-snapshot / attachment / last-prompt
 * - sidechain（subagent）目前一并跳过 —— 主对话流更纯
 * - 每个 assistant record 是一条 ChatMessage（一次模型 turn 内的多 record 不再合并 —— Claude 的 jsonl 已经分好了）
 * - tool_use 块映射为 ChatMessageBlock; thinking 不展示
 */
export function parseJsonlMessages(cwd, sessionId) {
    const path = claudeJsonlFile(cwd, sessionId);
    if (!existsSync(path))
        return [];
    let raw;
    try {
        raw = readFileSync(path, 'utf-8');
    }
    catch {
        return [];
    }
    const messages = [];
    for (const line of raw.split('\n')) {
        if (!line.trim())
            continue;
        let rec;
        try {
            rec = JSON.parse(line);
        }
        catch {
            continue;
        }
        if (rec.isMeta)
            continue;
        if (rec.isSidechain)
            continue;
        if (rec.type === 'user') {
            const m = recordToUserMessage(rec);
            if (m)
                messages.push(m);
        }
        else if (rec.type === 'assistant') {
            const m = recordToAssistantMessage(rec);
            if (m)
                messages.push(m);
        }
        // 其它 type（system / progress / file-history-snapshot / queue-operation /
        // attachment / last-prompt）不展示
    }
    return messages;
}
/**
 * 摘要（list() 用）。复用 parseJsonlMessages 保持计数与真实可展示条数一致 ——
 * 大文件下会读全文，但解析本身仍是 O(n) 行扫，对 list 的 SLA 足够。
 */
export function summarizeJsonl(cwd, sessionId) {
    const path = claudeJsonlFile(cwd, sessionId);
    if (!existsSync(path)) {
        return { exists: false, messageCount: 0, lastMessageAt: null, firstUserText: null, fileMTime: null };
    }
    let mtime = null;
    try {
        mtime = statSync(path).mtime.toISOString();
    }
    catch {
        /* ignore */
    }
    const msgs = parseJsonlMessages(cwd, sessionId);
    const lastTs = msgs.length > 0 ? msgs[msgs.length - 1].ts : null;
    let firstUser = null;
    for (const m of msgs) {
        if (m.role === 'user' && m.content.trim()) {
            firstUser = m.content.trim().slice(0, 120);
            break;
        }
    }
    return {
        exists: true,
        messageCount: msgs.length,
        lastMessageAt: lastTs,
        firstUserText: firstUser,
        fileMTime: mtime,
    };
}
// ─── 内部转换 ─────────────────────────────────────────────────────────────
function recordToUserMessage(rec) {
    const content = rec.message?.content;
    if (content == null)
        return null;
    let text = '';
    if (typeof content === 'string') {
        text = content;
    }
    else if (Array.isArray(content)) {
        // user content array 通常是 tool_result + text 混合 —— 只展示 text 块
        for (const blk of content) {
            if (blk && typeof blk === 'object' && blk.type === 'text') {
                text += blk.text;
            }
        }
        // 全是 tool_result 没有 text 的话，跳过这条记录（避免空气泡）
        if (text === '')
            return null;
    }
    // 过滤 Claude CLI 内部包装：slash 命令、stdout 注入、caveat 提示
    if (isClaudeMetaText(text))
        return null;
    return {
        id: rec.uuid ?? cryptoRandomId(),
        role: 'user',
        content: text,
        ts: rec.timestamp ?? new Date().toISOString(),
    };
}
/** 识别 Claude CLI 自动包装的非用户文本，避免污染 chat 视图 */
function isClaudeMetaText(text) {
    const t = text.trim();
    if (t === '')
        return true;
    if (t.startsWith('<command-name>'))
        return true;
    if (t.startsWith('<local-command-stdout>'))
        return true;
    if (t.startsWith('<local-command-stderr>'))
        return true;
    if (t.startsWith('<local-command-caveat>'))
        return true;
    if (t.startsWith('<system-reminder>'))
        return true;
    return false;
}
function recordToAssistantMessage(rec) {
    const blocks = rec.message?.content;
    if (!Array.isArray(blocks))
        return null;
    let text = '';
    const uiBlocks = [];
    for (const blk of blocks) {
        if (!blk || typeof blk !== 'object')
            continue;
        if (blk.type === 'text') {
            text += blk.text;
            uiBlocks.push({ type: 'text', text: blk.text });
        }
        else if (blk.type === 'tool_use') {
            uiBlocks.push({
                type: 'tool_use',
                id: blk.id,
                title: blk.name,
                kind: blk.name,
                status: 'complete',
            });
        }
        // thinking 块刻意不展示（签名+原文较长，后端没必要给前端）
    }
    if (text === '' && uiBlocks.length === 0)
        return null;
    return {
        id: rec.uuid ?? cryptoRandomId(),
        role: 'assistant',
        content: text,
        blocks: uiBlocks.length > 0 ? uiBlocks : undefined,
        ts: rec.timestamp ?? new Date().toISOString(),
    };
}
function cryptoRandomId() {
    // jsonl 必有 uuid；这里只是兜底，避免 strict TS 抱怨
    return `gen-${Math.random().toString(36).slice(2, 12)}`;
}
//# sourceMappingURL=claudeJsonl.js.map