"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.transcriptExists = transcriptExists;
exports.deleteTranscript = deleteTranscript;
exports.countTranscriptMessages = countTranscriptMessages;
exports.readMoreLines = readMoreLines;
exports.loadLastUsage = loadLastUsage;
exports.loadTranscript = loadTranscript;
exports.loadMoreMessages = loadMoreMessages;
exports.findSessionCwd = findSessionCwd;
exports.scanAllSessions = scanAllSessions;
exports.scanSessionsForCwd = scanSessionsForCwd;
exports.scanSessionsByIds = scanSessionsByIds;
exports.listAllTranscriptFiles = listAllTranscriptFiles;
exports.globalSearch = globalSearch;
const node_fs_1 = require("node:fs");
const promises_1 = require("node:fs/promises");
const node_os_1 = require("node:os");
const node_path_1 = __importDefault(require("node:path"));
const node_readline_1 = require("node:readline");
const uuid_1 = require("uuid");
const debug_logger_1 = require("./debug-logger");
function getTranscriptPath(sessionId, cwd) {
    const projectKey = cwd.replace(/[/.]/g, "-");
    return node_path_1.default.join((0, node_os_1.homedir)(), ".claude", "projects", projectKey, `${sessionId}.jsonl`);
}
function transcriptExists(sessionId, cwd) {
    return (0, node_fs_1.existsSync)(getTranscriptPath(sessionId, cwd));
}
async function deleteTranscript(sessionId, cwd) {
    const fp = getTranscriptPath(sessionId, cwd);
    if (!(0, node_fs_1.existsSync)(fp))
        return false;
    await (0, promises_1.unlink)(fp);
    return true;
}
function countTranscriptMessages(sessionId, cwd) {
    const fp = getTranscriptPath(sessionId, cwd);
    if (!(0, node_fs_1.existsSync)(fp))
        return 0;
    try {
        const raw = (0, node_fs_1.readFileSync)(fp, "utf-8");
        let count = 0;
        for (const line of raw.split("\n")) {
            if (line.includes('"role"') && (line.includes('"assistant"') || line.includes('"user"'))) {
                count++;
            }
        }
        return count;
    }
    catch {
        return 0;
    }
}
const CLI_XML_RE = /<(?:task-notification|local-command-caveat|local-command-stdout|system-reminder)[^>]*>[\s\S]*?<\/(?:task-notification|local-command-caveat|local-command-stdout|system-reminder)>[\s\S]*/g;
const FILE_TAG_RE = /<file\s+path="([^"]+)">\n([\s\S]*?)\n<\/file>/g;
function extractTextFiles(text) {
    const textFiles = [];
    const cleaned = text
        .replace(FILE_TAG_RE, (_match, name, content) => {
        textFiles.push({ name, content });
        return "";
    })
        .replace(/\n{3,}/g, "\n\n")
        .trim();
    return { cleaned, textFiles };
}
function stripCommandXml(text) {
    const trimmed = text.trimStart();
    if (trimmed.startsWith("<task-notification>"))
        return "";
    if (trimmed.startsWith("<local-command-caveat>"))
        return "";
    if (trimmed.startsWith("<local-command-stdout>"))
        return "";
    if (trimmed.startsWith("<command-name>")) {
        const match = trimmed.match(/<command-name>(\/[^<]+)<\/command-name>/);
        if (match) {
            if (match[1] === "/compact")
                return "";
            return match[1];
        }
    }
    return text;
}
function stripCliXml(text) {
    return text.replace(CLI_XML_RE, "").trim();
}
const INITIAL_CHUNK_SIZE = 64 * 1024; // 64KB
async function readTailLines(filePath, targetCount) {
    const fileStat = await (0, promises_1.stat)(filePath);
    const totalSize = fileStat.size;
    if (totalSize === 0)
        return { lines: [], byteOffset: 0, totalSize };
    let chunkSize = INITIAL_CHUNK_SIZE;
    const fh = await (0, promises_1.open)(filePath, "r");
    try {
        while (true) {
            const readSize = Math.min(chunkSize, totalSize);
            const offset = totalSize - readSize;
            const buf = Buffer.alloc(readSize);
            await fh.read(buf, 0, readSize, offset);
            const text = buf.toString("utf-8");
            // Split into lines, discard partial first line if we didn't read from start
            let lines;
            if (offset > 0) {
                const firstNewline = text.indexOf("\n");
                if (firstNewline === -1) {
                    // Entire chunk is one partial line, need a bigger chunk
                    if (chunkSize >= totalSize) {
                        lines = [text];
                    }
                    else {
                        chunkSize *= 2;
                        continue;
                    }
                }
                else {
                    lines = text.slice(firstNewline + 1).split(/\r?\n/);
                }
            }
            else {
                lines = text.split(/\r?\n/);
            }
            lines = lines.filter((l) => l.trim());
            if (lines.length >= targetCount || offset === 0) {
                // Take only the last targetCount lines
                const result = lines.length > targetCount ? lines.slice(-targetCount) : lines;
                // Calculate byte offset of the first returned line
                // We approximate by finding position in the chunk text
                const returnedText = result.join("\n");
                const byteOffset = Math.max(0, offset + (text.length - returnedText.length - (text.endsWith("\n") ? 1 : 0)));
                return { lines: result, byteOffset: offset === 0 ? 0 : byteOffset, totalSize };
            }
            // Not enough lines, double chunk and retry
            chunkSize *= 2;
        }
    }
    finally {
        await fh.close();
    }
}
async function readMoreLines(filePath, byteOffset, targetCount) {
    if (byteOffset <= 0)
        return { lines: [], newByteOffset: 0 };
    let chunkSize = INITIAL_CHUNK_SIZE;
    const fh = await (0, promises_1.open)(filePath, "r");
    try {
        while (true) {
            const readSize = Math.min(chunkSize, byteOffset);
            const offset = byteOffset - readSize;
            const buf = Buffer.alloc(readSize);
            await fh.read(buf, 0, readSize, offset);
            const text = buf.toString("utf-8");
            let lines;
            if (offset > 0) {
                const firstNewline = text.indexOf("\n");
                if (firstNewline === -1) {
                    if (chunkSize >= byteOffset) {
                        lines = [text];
                    }
                    else {
                        chunkSize *= 2;
                        continue;
                    }
                }
                else {
                    lines = text.slice(firstNewline + 1).split(/\r?\n/);
                }
            }
            else {
                lines = text.split(/\r?\n/);
            }
            lines = lines.filter((l) => l.trim());
            if (lines.length >= targetCount || offset === 0) {
                const result = lines.length > targetCount ? lines.slice(-targetCount) : lines;
                const returnedText = result.join("\n");
                const newByteOffset = Math.max(0, offset + (text.length - returnedText.length - (text.endsWith("\n") ? 1 : 0)));
                return { lines: result, newByteOffset: offset === 0 ? 0 : newByteOffset };
            }
            chunkSize *= 2;
        }
    }
    finally {
        await fh.close();
    }
}
async function loadLastUsage(sessionId, cwd) {
    const fp = getTranscriptPath(sessionId, cwd);
    if (!(0, node_fs_1.existsSync)(fp))
        return null;
    const raw = await (0, promises_1.readFile)(fp, "utf-8");
    const lines = raw.split(/\r?\n/).filter((l) => l.trim());
    let lastUsage = null;
    let contextWindowSize = 200_000;
    // Scan backwards for the most recent result event with modelUsage to get contextWindow
    for (let i = lines.length - 1; i >= 0; i--) {
        try {
            const entry = JSON.parse(lines[i]);
            if (entry.type === "result" && entry.modelUsage) {
                for (const model of Object.values(entry.modelUsage)) {
                    if (model.contextWindow && model.contextWindow > 0) {
                        contextWindowSize = model.contextWindow;
                        break;
                    }
                }
                break;
            }
        }
        catch { }
    }
    for (let i = lines.length - 1; i >= 0; i--) {
        try {
            const entry = JSON.parse(lines[i]);
            if (entry.type === "assistant" && entry.message?.usage) {
                const u = entry.message.usage;
                const used = (u.input_tokens || 0) + (u.cache_creation_input_tokens || 0) + (u.cache_read_input_tokens || 0);
                lastUsage = { used, total: contextWindowSize };
                break;
            }
        }
        catch { }
    }
    return lastUsage;
}
function parseLines(lines) {
    const messages = [];
    const messageById = new Map();
    const toolUseMap = new Map();
    let lastUserOrToolResultTs = 0;
    let lastUsage = null;
    let contextWindowSize = 200_000;
    for (const line of lines) {
        let entry;
        try {
            entry = JSON.parse(line);
        }
        catch {
            continue;
        }
        // Extract usage data as we parse
        if (entry.type === "result") {
            const modelUsage = entry.modelUsage;
            if (modelUsage) {
                for (const model of Object.values(modelUsage)) {
                    if (model.contextWindow && model.contextWindow > 0) {
                        contextWindowSize = model.contextWindow;
                        break;
                    }
                }
            }
            continue;
        }
        if (entry.type === "assistant" && entry.message) {
            const usage = entry.message.usage;
            if (usage) {
                const used = (usage.input_tokens || 0) + (usage.cache_creation_input_tokens || 0) + (usage.cache_read_input_tokens || 0);
                lastUsage = { used, total: contextWindowSize };
            }
        }
        // Attach sub-agent tool calls as children of their parent Agent tool
        if (entry.type === "progress" && entry.parentToolUseID) {
            const parentTool = toolUseMap.get(entry.parentToolUseID);
            if (!parentTool)
                continue;
            const innerMsg = entry.data?.message;
            const content = innerMsg?.message?.content;
            if (!innerMsg || !Array.isArray(content))
                continue;
            if (innerMsg.type === "assistant") {
                if (!parentTool.children)
                    parentTool.children = [];
                for (const block of content) {
                    if (block.type === "tool_use") {
                        const child = {
                            id: block.id || (0, uuid_1.v4)(),
                            name: block.name || "unknown",
                            input: block.input ? JSON.stringify(block.input) : "",
                            output: "",
                            status: "done",
                        };
                        parentTool.children.push(child);
                        toolUseMap.set(child.id, child);
                    }
                }
            }
            else if (innerMsg.type === "user") {
                for (const tr of content) {
                    if (tr.type !== "tool_result")
                        continue;
                    const tool = toolUseMap.get(tr.tool_use_id || "");
                    if (tool) {
                        tool.output = extractOutput(tr);
                        tool.status = "done";
                    }
                }
            }
            continue;
        }
        // Skip meta messages (slash command caveats, etc.)
        if (entry.isMeta)
            continue;
        if (entry.type === "system" && entry.subtype === "local_command" && entry.content) {
            const rawContent = entry.content;
            const match = rawContent.match(/<local-command-stdout>([\s\S]*?)<\/local-command-stdout>/);
            const text = match ? match[1].trim() : rawContent;
            if (text) {
                messages.push({
                    id: entry.uuid || (0, uuid_1.v4)(),
                    role: "assistant",
                    content: text,
                    toolUses: [],
                    blocks: [{ type: "text", text }],
                    timestamp: entry.timestamp ? new Date(entry.timestamp).getTime() : Date.now(),
                });
            }
            continue;
        }
        if (entry.type === "system" && entry.subtype === "compact_boundary") {
            messages.push({
                id: "compact-" + (0, uuid_1.v4)(),
                role: "system",
                content: "__compacted__",
                toolUses: [],
                blocks: [],
                timestamp: entry.timestamp ? new Date(entry.timestamp).getTime() : Date.now(),
            });
            continue;
        }
        if (entry.type === "user" && entry.message) {
            if (entry.timestamp)
                lastUserOrToolResultTs = new Date(entry.timestamp).getTime();
            const content = entry.message.content;
            if (typeof content === "string") {
                const stripped = stripCommandXml(content);
                if (stripped) {
                    const { cleaned, textFiles } = extractTextFiles(stripped);
                    if (cleaned || textFiles.length > 0) {
                        messages.push({
                            id: entry.message.id || (0, uuid_1.v4)(),
                            role: "user",
                            content: cleaned,
                            toolUses: [],
                            blocks: [],
                            timestamp: entry.timestamp ? new Date(entry.timestamp).getTime() : Date.now(),
                            textFiles: textFiles.length > 0 ? textFiles : undefined,
                        });
                    }
                }
                continue;
            }
            if (Array.isArray(content)) {
                const toolResults = content.filter((b) => b.type === "tool_result");
                for (const tr of toolResults) {
                    const toolId = tr.tool_use_id || "";
                    const tool = toolUseMap.get(toolId);
                    if (tool) {
                        tool.output = extractOutput(tr);
                        tool.status = "done";
                    }
                }
                const imageBlocks = content.filter((b) => b.type === "image" && b.source?.type === "base64" && b.source.media_type && b.source.data);
                const images = imageBlocks.map((b) => ({
                    mediaType: b.source.media_type,
                    data: b.source.data,
                }));
                const docBlocks = content.filter((b) => b.type === "document" && b.source?.type === "base64" && b.source.media_type === "application/pdf" && b.source.data);
                const documents = docBlocks.map((b) => ({
                    mediaType: "application/pdf",
                    data: b.source.data,
                    name: "document.pdf",
                }));
                const textParts = content.filter((b) => b.type === "text" && b.text).map((b) => b.text);
                const userText = textParts.join("\n");
                if (images.length > 0 || documents.length > 0 || (userText && toolResults.length === 0)) {
                    const stripped = userText ? stripCommandXml(userText) : "";
                    const { cleaned, textFiles } = extractTextFiles(stripped);
                    if (images.length > 0 || documents.length > 0 || cleaned || textFiles.length > 0) {
                        messages.push({
                            id: entry.message.id || (0, uuid_1.v4)(),
                            role: "user",
                            content: cleaned,
                            toolUses: [],
                            blocks: [],
                            timestamp: entry.timestamp ? new Date(entry.timestamp).getTime() : Date.now(),
                            images: images.length > 0 ? images : undefined,
                            documents: documents.length > 0 ? documents : undefined,
                            textFiles: textFiles.length > 0 ? textFiles : undefined,
                        });
                    }
                }
            }
        }
        if (entry.type === "assistant" && entry.message) {
            const content = entry.message.content;
            if (!Array.isArray(content))
                continue;
            const toolUses = [];
            const blocks = [];
            let textContent = "";
            const msgId = entry.message.id || (0, uuid_1.v4)();
            const entryTs = entry.timestamp ? new Date(entry.timestamp).getTime() : 0;
            const hasThinking = content.some((b) => b.type === "thinking" && (b.thinking || b.signature));
            let thinkingDurationMs;
            if (hasThinking && lastUserOrToolResultTs && entryTs > lastUserOrToolResultTs) {
                thinkingDurationMs = entryTs - lastUserOrToolResultTs;
            }
            for (const block of content) {
                if (block.type === "thinking") {
                    const redacted = !block.thinking && !!block.signature;
                    if (!block.thinking && !redacted)
                        continue;
                    blocks.push({ type: "thinking", text: block.thinking ?? "", durationMs: thinkingDurationMs, redacted });
                }
                else if (block.type === "text" && block.text) {
                    const cleaned = stripCliXml(block.text);
                    if (cleaned) {
                        textContent += cleaned;
                        blocks.push({ type: "text", text: cleaned });
                    }
                }
                else if (block.type === "tool_use") {
                    const tool = {
                        id: block.id || (0, uuid_1.v4)(),
                        name: block.name || "unknown",
                        input: block.input ? JSON.stringify(block.input) : "",
                        output: "",
                        status: "done",
                    };
                    toolUses.push(tool);
                    blocks.push({ type: "tool_use", toolUse: tool });
                    toolUseMap.set(tool.id, tool);
                }
            }
            const trimmed = textContent.trim();
            if (toolUses.length === 0 && (trimmed === "No response requested." || /^API Error: \d+\s/.test(trimmed))) {
                continue;
            }
            const existing = entry.message.id ? messageById.get(msgId) : undefined;
            if (existing) {
                if (textContent)
                    existing.content += textContent;
                for (const tu of toolUses) {
                    if (!existing.toolUses.some((t) => t.id === tu.id)) {
                        existing.toolUses.push(tu);
                    }
                }
                for (const b of blocks) {
                    if (b.type === "tool_use") {
                        if (existing.blocks.some((eb) => eb.type === "tool_use" && eb.toolUse.id === b.toolUse.id))
                            continue;
                    }
                    existing.blocks.push(b);
                }
            }
            else {
                const msg = {
                    id: msgId,
                    role: "assistant",
                    content: textContent,
                    toolUses,
                    blocks,
                    timestamp: entry.timestamp ? new Date(entry.timestamp).getTime() : Date.now(),
                    model: typeof entry.message.model === "string" ? entry.message.model : undefined,
                };
                messages.push(msg);
                messageById.set(msgId, msg);
            }
        }
    }
    return { messages, lastUsage };
}
const TAIL_LINES = 150;
const _PAGE_SIZE = 50;
async function loadTranscript(sessionId, cwd, options) {
    const fp = getTranscriptPath(sessionId, cwd);
    if (!(0, node_fs_1.existsSync)(fp))
        return { messages: [], byteOffset: 0, totalSize: 0, lastUsage: null };
    const t0 = performance.now();
    const tailCount = options?.tailLines;
    let lines;
    let byteOffset;
    let totalSize;
    let readLabel;
    if (tailCount) {
        const result = await readTailLines(fp, tailCount);
        lines = result.lines;
        byteOffset = result.byteOffset;
        totalSize = result.totalSize;
        readLabel = `tail(${tailCount})`;
    }
    else {
        const raw = await (0, promises_1.readFile)(fp, "utf-8");
        lines = raw.split(/\r?\n/).filter((l) => l.trim());
        byteOffset = 0;
        totalSize = raw.length;
        readLabel = "full";
    }
    const tRead = performance.now();
    const { messages, lastUsage } = parseLines(lines);
    const tParse = performance.now();
    const sid = sessionId.slice(0, 8);
    (0, debug_logger_1.debugLog)(`[transcript:${sid}] ${readLabel}: ${lines.length} lines, ${messages.length} msgs | read=${(tRead - t0).toFixed(0)}ms parse=${(tParse - tRead).toFixed(0)}ms total=${(tParse - t0).toFixed(0)}ms`);
    return { messages, byteOffset, totalSize, lastUsage };
}
async function loadMoreMessages(sessionId, cwd, byteOffset, targetLines) {
    if (byteOffset <= 0)
        return { messages: [], newByteOffset: 0 };
    const fp = getTranscriptPath(sessionId, cwd);
    const t0 = performance.now();
    const { lines, newByteOffset } = await readMoreLines(fp, byteOffset, targetLines || TAIL_LINES);
    const tRead = performance.now();
    const { messages } = parseLines(lines);
    const tParse = performance.now();
    const sid = sessionId.slice(0, 8);
    (0, debug_logger_1.debugLog)(`[transcript:${sid}] more: ${lines.length} lines, ${messages.length} msgs | read=${(tRead - t0).toFixed(0)}ms parse=${(tParse - tRead).toFixed(0)}ms`);
    return { messages, newByteOffset };
}
function extractOutput(block) {
    if (typeof block.content === "string")
        return block.content;
    if (Array.isArray(block.content)) {
        return block.content
            .map((b) => {
            if (typeof b === "string")
                return b;
            if (b.type === "text" && b.text)
                return b.text;
            return "";
        })
            .filter(Boolean)
            .join("\n");
    }
    return "";
}
async function extractSessionMeta(filePath) {
    const id = node_path_1.default.basename(filePath, ".jsonl");
    let cwd = "";
    let title = "";
    let createdAt = 0;
    try {
        const fileStat = await (0, promises_1.stat)(filePath);
        const lastActiveAt = fileStat.mtimeMs;
        const rl = (0, node_readline_1.createInterface)({
            input: (0, node_fs_1.createReadStream)(filePath, { encoding: "utf-8" }),
            crlfDelay: Infinity,
        });
        let linesRead = 0;
        for await (const line of rl) {
            if (linesRead > 50)
                break;
            linesRead++;
            let entry;
            try {
                entry = JSON.parse(line);
            }
            catch {
                continue;
            }
            if (entry.type === "user" && entry.cwd && !cwd) {
                cwd = entry.cwd;
            }
            if (entry.timestamp && !createdAt) {
                createdAt = new Date(entry.timestamp).getTime();
            }
            if (entry.type === "user" && entry.message && !title) {
                const content = entry.message.content;
                let candidate = "";
                if (typeof content === "string") {
                    candidate = content;
                }
                else if (Array.isArray(content)) {
                    const textBlock = content.find((b) => b.type === "text" && b.text);
                    if (textBlock?.text)
                        candidate = textBlock.text;
                }
                // Skip system-generated messages like [Request interrupted...] and XML tags
                if (candidate && !candidate.startsWith("[") && !candidate.startsWith("<")) {
                    title = candidate.slice(0, 120);
                }
            }
            if (cwd && title && createdAt)
                break;
        }
        rl.close();
        if (!cwd)
            return null;
        return {
            id,
            cwd,
            title: title || "Untitled session",
            createdAt: createdAt || lastActiveAt,
            lastActiveAt,
        };
    }
    catch {
        return null;
    }
}
async function findSessionCwd(sessionId) {
    const projectsDir = node_path_1.default.join((0, node_os_1.homedir)(), ".claude", "projects");
    if (!(0, node_fs_1.existsSync)(projectsDir))
        return null;
    let projectDirs;
    try {
        projectDirs = await (0, promises_1.readdir)(projectsDir);
    }
    catch {
        return null;
    }
    const filename = `${sessionId}.jsonl`;
    for (const dir of projectDirs) {
        const filePath = node_path_1.default.join(projectsDir, dir, filename);
        if ((0, node_fs_1.existsSync)(filePath)) {
            const meta = await extractSessionMeta(filePath);
            if (meta?.cwd)
                return meta.cwd;
            return null;
        }
    }
    return null;
}
async function scanAllSessions() {
    const projectsDir = node_path_1.default.join((0, node_os_1.homedir)(), ".claude", "projects");
    if (!(0, node_fs_1.existsSync)(projectsDir))
        return [];
    let projectDirs;
    try {
        projectDirs = await (0, promises_1.readdir)(projectsDir);
    }
    catch {
        return [];
    }
    const allMeta = [];
    for (const dir of projectDirs) {
        const dirPath = node_path_1.default.join(projectsDir, dir);
        let files;
        try {
            files = await (0, promises_1.readdir)(dirPath);
        }
        catch {
            continue;
        }
        const jsonlFiles = files.filter((f) => f.endsWith(".jsonl"));
        const metas = await Promise.all(jsonlFiles.map((f) => extractSessionMeta(node_path_1.default.join(dirPath, f))));
        for (const meta of metas) {
            if (meta)
                allMeta.push(meta);
        }
    }
    const groups = new Map();
    for (const meta of allMeta) {
        const sessions = groups.get(meta.cwd) || [];
        sessions.push({
            id: meta.id,
            name: meta.title,
            cwd: meta.cwd,
            createdAt: meta.createdAt,
            lastActiveAt: meta.lastActiveAt,
            status: "idle",
        });
        groups.set(meta.cwd, sessions);
    }
    const result = [];
    for (const [cwd, sessions] of groups) {
        sessions.sort((a, b) => b.lastActiveAt - a.lastActiveAt);
        result.push({
            cwd,
            dirName: node_path_1.default.basename(cwd) || cwd,
            sessions,
            totalSessionCount: sessions.length,
        });
    }
    result.sort((a, b) => {
        const aLatest = a.sessions[0]?.lastActiveAt || 0;
        const bLatest = b.sessions[0]?.lastActiveAt || 0;
        return bLatest - aLatest;
    });
    return result;
}
function metaToSessionInfo(meta) {
    return {
        id: meta.id,
        name: meta.title,
        cwd: meta.cwd,
        createdAt: meta.createdAt,
        lastActiveAt: meta.lastActiveAt,
        status: "idle",
    };
}
async function scanSessionsForCwd(targetCwd) {
    const projectsDir = node_path_1.default.join((0, node_os_1.homedir)(), ".claude", "projects");
    if (!(0, node_fs_1.existsSync)(projectsDir))
        return [];
    let projectDirs;
    try {
        projectDirs = await (0, promises_1.readdir)(projectsDir);
    }
    catch {
        return [];
    }
    const results = [];
    for (const dir of projectDirs) {
        const dirPath = node_path_1.default.join(projectsDir, dir);
        let files;
        try {
            files = await (0, promises_1.readdir)(dirPath);
        }
        catch {
            continue;
        }
        const jsonlFiles = files.filter((f) => f.endsWith(".jsonl"));
        const metas = await Promise.all(jsonlFiles.map((f) => extractSessionMeta(node_path_1.default.join(dirPath, f))));
        for (const meta of metas) {
            if (meta && meta.cwd === targetCwd) {
                results.push(metaToSessionInfo(meta));
            }
        }
    }
    results.sort((a, b) => b.lastActiveAt - a.lastActiveAt);
    return results;
}
async function scanSessionsByIds(ids) {
    if (ids.length === 0)
        return [];
    const projectsDir = node_path_1.default.join((0, node_os_1.homedir)(), ".claude", "projects");
    if (!(0, node_fs_1.existsSync)(projectsDir))
        return [];
    let projectDirs;
    try {
        projectDirs = await (0, promises_1.readdir)(projectsDir);
    }
    catch {
        return [];
    }
    const idSet = new Set(ids);
    const results = [];
    for (const dir of projectDirs) {
        if (idSet.size === 0)
            break;
        const dirPath = node_path_1.default.join(projectsDir, dir);
        let files;
        try {
            files = await (0, promises_1.readdir)(dirPath);
        }
        catch {
            continue;
        }
        const matching = files.filter((f) => f.endsWith(".jsonl") && idSet.has(f.slice(0, -".jsonl".length)));
        if (matching.length === 0)
            continue;
        const metas = await Promise.all(matching.map((f) => extractSessionMeta(node_path_1.default.join(dirPath, f))));
        for (const meta of metas) {
            if (meta) {
                results.push(metaToSessionInfo(meta));
                idSet.delete(meta.id);
            }
        }
    }
    return results;
}
async function listAllTranscriptFiles() {
    const projectsDir = node_path_1.default.join((0, node_os_1.homedir)(), ".claude", "projects");
    if (!(0, node_fs_1.existsSync)(projectsDir))
        return [];
    let projectDirs;
    try {
        projectDirs = await (0, promises_1.readdir)(projectsDir);
    }
    catch {
        return [];
    }
    const results = [];
    for (const dir of projectDirs) {
        if (dir.includes(".cockpit"))
            continue;
        const dirPath = node_path_1.default.join(projectsDir, dir);
        let files;
        try {
            files = await (0, promises_1.readdir)(dirPath);
        }
        catch {
            continue;
        }
        for (const f of files) {
            if (!f.endsWith(".jsonl"))
                continue;
            const filePath = node_path_1.default.join(dirPath, f);
            try {
                const s = await (0, promises_1.stat)(filePath);
                results.push({ filePath, sessionId: f.slice(0, -6), mtimeMs: s.mtimeMs });
            }
            catch { }
        }
    }
    results.sort((a, b) => b.mtimeMs - a.mtimeMs);
    return results;
}
async function globalSearch(query, limit, offset = 0) {
    const MAX_FILES = 500;
    const MAX_PARSED_BASE = 30;
    const MAX_PER_FILE = 10;
    const effectiveLimit = Math.min(limit, 100);
    const lowerQuery = query.toLowerCase();
    // When paginating, allow parsing more files to reach the offset
    const maxParsed = MAX_PARSED_BASE + Math.ceil(offset / MAX_PER_FILE);
    const files = await listAllTranscriptFiles();
    const searchFiles = files.slice(0, MAX_FILES);
    const results = [];
    let totalFound = 0;
    let filesParsed = 0;
    let truncated = false;
    for (const file of searchFiles) {
        if (results.length >= effectiveLimit || filesParsed >= maxParsed) {
            truncated = true;
            break;
        }
        let raw;
        try {
            raw = await (0, promises_1.readFile)(file.filePath, "utf-8");
        }
        catch {
            continue;
        }
        if (!raw.toLowerCase().includes(lowerQuery))
            continue;
        filesParsed++;
        const lines = raw.split(/\r?\n/).filter((l) => l.trim());
        const { messages } = parseLines(lines);
        let cwd = "";
        let sessionTitle = "";
        for (let j = 0; j < Math.min(lines.length, 50); j++) {
            try {
                const entry = JSON.parse(lines[j]);
                if (entry.type === "user" && entry.cwd && !cwd)
                    cwd = entry.cwd;
                if (entry.type === "user" && entry.message && !sessionTitle) {
                    const content = entry.message.content;
                    let candidate = "";
                    if (typeof content === "string")
                        candidate = content;
                    else if (Array.isArray(content)) {
                        const tb = content.find((b) => b.type === "text" && b.text);
                        if (tb?.text)
                            candidate = tb.text;
                    }
                    if (candidate && !candidate.startsWith("[") && !candidate.startsWith("<")) {
                        sessionTitle = candidate.slice(0, 120);
                    }
                }
                if (cwd && sessionTitle)
                    break;
            }
            catch { }
        }
        if (!cwd)
            continue;
        if (cwd.endsWith(".cockpit/reviews") || cwd.endsWith(".cockpit/jobs"))
            continue;
        const dirName = node_path_1.default.basename(cwd) || cwd;
        let fileResults = 0;
        for (let i = 0; i < messages.length; i++) {
            if (fileResults >= MAX_PER_FILE || results.length >= effectiveLimit)
                break;
            const msg = messages[i];
            if (msg.role !== "user" && msg.role !== "assistant")
                continue;
            if (i > 0 && messages[i - 1].content === "__compacted__")
                continue;
            if (i > 1 && messages[i - 2].content === "__compacted__")
                continue;
            let text = "";
            if (msg.blocks && msg.blocks.length > 0) {
                text = msg.blocks
                    .filter((b) => b.type === "text")
                    .map((b) => b.text)
                    .join("\n");
            }
            if (!text && typeof msg.content === "string") {
                text = msg.content;
            }
            if (!text)
                continue;
            const matchIndex = text.toLowerCase().indexOf(lowerQuery);
            if (matchIndex === -1)
                continue;
            totalFound++;
            fileResults++;
            if (totalFound <= offset)
                continue;
            const previewStart = Math.max(0, matchIndex - 100);
            const previewEnd = Math.min(text.length, matchIndex + query.length + 100);
            let preview = text.slice(previewStart, previewEnd);
            let matchStart = matchIndex - previewStart;
            if (previewStart > 0) {
                preview = "..." + preview;
                matchStart += 3;
            }
            if (previewEnd < text.length) {
                preview = preview + "...";
            }
            results.push({
                sessionId: file.sessionId,
                sessionName: sessionTitle || "Untitled session",
                cwd,
                dirName,
                messageId: msg.id,
                role: msg.role,
                timestamp: msg.timestamp,
                preview,
                matchStart,
                matchLength: query.length,
                fullContent: text.length > 2000 ? text.slice(0, 2000) + "..." : text,
            });
        }
    }
    results.sort((a, b) => b.timestamp - a.timestamp);
    return { results, totalFilesSearched: searchFiles.length, truncated };
}
