/**
 * @license
 * Copyright 2025 Google LLC
 * SPDX-License-Identifier: Apache-2.0
 */
import {} from '../utils/thoughtUtils.js';
import { getProjectHash } from '../utils/paths.js';
import path from 'node:path';
import * as fs from 'node:fs';
import { sanitizeFilenamePart } from '../utils/fileUtils.js';
import { isNodeError } from '../utils/errors.js';
import { deleteSessionArtifactsAsync, deleteStoredSession, } from '../utils/sessionOperations.js';
import readline from 'node:readline';
import { randomUUID } from 'node:crypto';
import { debugLogger } from '../utils/debugLogger.js';
import { partListUnionToString } from '../core/geminiRequest.js';
import { isIgnoredUserContent } from '../utils/sessionUtils.js';
import { SESSION_FILE_PREFIX, } from './chatRecordingTypes.js';
export * from './chatRecordingTypes.js';
/**
 * Warning message shown when recording is disabled due to disk full.
 */
const ENOSPC_WARNING_MESSAGE = 'Chat recording disabled: No space left on device. ' +
    'The conversation will continue but will not be saved to disk. ' +
    'Free up disk space and restart to enable recording.';
function hasProperty(obj, prop) {
    return obj !== null && typeof obj === 'object' && prop in obj;
}
function isStringProperty(obj, prop) {
    return hasProperty(obj, prop) && typeof obj[prop] === 'string';
}
function isObjectProperty(obj, prop) {
    return (hasProperty(obj, prop) &&
        obj[prop] !== null &&
        typeof obj[prop] === 'object');
}
function isRewindRecord(record) {
    return isStringProperty(record, '$rewindTo');
}
function isMessageRecord(record) {
    return isStringProperty(record, 'id');
}
function isMetadataUpdateRecord(record) {
    return isObjectProperty(record, '$set');
}
function isPartialMetadataRecord(record) {
    return (isStringProperty(record, 'sessionId') &&
        isStringProperty(record, 'projectHash'));
}
function isTextPart(part) {
    return isStringProperty(part, 'text');
}
/**
 * Returns true when a stored message represents conversation content worth
 * surfacing in resume flows.
 */
export function isResumableMessageRecord(message) {
    const contentString = message.content
        ? partListUnionToString(message.content)
        : '';
    if (message.type === 'user') {
        return !isIgnoredUserContent(contentString.trim());
    }
    if (message.type === 'gemini') {
        return (contentString.trim().length > 0 ||
            (message.toolCalls?.length ?? 0) > 0 ||
            (message.thoughts?.length ?? 0) > 0);
    }
    return false;
}
export function hasResumableConversationContent(messages) {
    return messages.some((message) => isResumableMessageRecord(message));
}
export async function loadConversationRecord(filePath, options) {
    if (!fs.existsSync(filePath)) {
        return null;
    }
    try {
        const fileStream = fs.createReadStream(filePath);
        const rl = readline.createInterface({
            input: fileStream,
            crlfDelay: Infinity,
        });
        let metadata = {};
        const messagesMap = new Map();
        const messageIds = [];
        const messageKinds = new Map();
        let isTrackingMemoryScratchpadFreshness = false;
        let memoryScratchpadIsStale = false;
        let firstUserMessageStr;
        for await (const line of rl) {
            if (!line.trim())
                continue;
            try {
                const record = JSON.parse(line);
                if (isRewindRecord(record)) {
                    if (isTrackingMemoryScratchpadFreshness) {
                        memoryScratchpadIsStale = true;
                    }
                    const rewindId = record.$rewindTo;
                    if (options?.metadataOnly) {
                        const idx = messageIds.indexOf(rewindId);
                        if (idx !== -1) {
                            const removedIds = messageIds.splice(idx);
                            for (const removedId of removedIds) {
                                messageKinds.delete(removedId);
                            }
                        }
                        else {
                            messageIds.length = 0;
                            messageKinds.clear();
                        }
                    }
                    else {
                        let found = false;
                        const idsToDelete = [];
                        for (const [id] of messagesMap) {
                            if (id === rewindId)
                                found = true;
                            if (found)
                                idsToDelete.push(id);
                        }
                        if (found) {
                            for (const id of idsToDelete) {
                                messagesMap.delete(id);
                            }
                        }
                        else {
                            messagesMap.clear();
                        }
                    }
                }
                else if (isMessageRecord(record)) {
                    if (isTrackingMemoryScratchpadFreshness) {
                        memoryScratchpadIsStale = true;
                    }
                    const id = record.id;
                    const isUser = hasProperty(record, 'type') && record.type === 'user';
                    const isResumable = isResumableMessageRecord(record);
                    // Track message count and first user message
                    if (options?.metadataOnly) {
                        messageIds.push(id);
                        messageKinds.set(id, { isUser, isResumable });
                    }
                    if (!firstUserMessageStr &&
                        isUser &&
                        hasProperty(record, 'content') &&
                        record['content'] &&
                        isResumable) {
                        // Basic extraction of first user message for display
                        const rawContent = record['content'];
                        if (Array.isArray(rawContent)) {
                            firstUserMessageStr = rawContent
                                .map((p) => (isTextPart(p) ? p['text'] : ''))
                                .join('');
                        }
                        else if (typeof rawContent === 'string') {
                            firstUserMessageStr = rawContent;
                        }
                    }
                    if (!options?.metadataOnly) {
                        messagesMap.set(id, record);
                        if (options?.maxMessages &&
                            messagesMap.size > options.maxMessages) {
                            const firstKey = messagesMap.keys().next().value;
                            if (typeof firstKey === 'string')
                                messagesMap.delete(firstKey);
                        }
                    }
                }
                else if (isMetadataUpdateRecord(record)) {
                    if (hasProperty(record.$set, 'memoryScratchpad')) {
                        isTrackingMemoryScratchpadFreshness = Boolean(record.$set.memoryScratchpad);
                        memoryScratchpadIsStale = false;
                    }
                    if (hasProperty(record.$set, 'messages') &&
                        Array.isArray(record.$set.messages)) {
                        // Checkpoint: clear and rebuild from the provided messages array
                        messagesMap.clear();
                        if (options?.metadataOnly) {
                            messageIds.length = 0;
                            messageKinds.clear();
                        }
                        for (const msg of record.$set.messages) {
                            if (isMessageRecord(msg)) {
                                const id = msg.id;
                                const isUser = msg.type === 'user';
                                const isResumable = isResumableMessageRecord(msg);
                                if (options?.metadataOnly) {
                                    messageIds.push(id);
                                    messageKinds.set(id, {
                                        isUser,
                                        isResumable,
                                    });
                                }
                                else {
                                    messagesMap.set(id, msg);
                                }
                                if (!firstUserMessageStr &&
                                    isUser &&
                                    isResumable &&
                                    msg.content &&
                                    (Array.isArray(msg.content) ||
                                        typeof msg.content === 'string')) {
                                    if (Array.isArray(msg.content)) {
                                        firstUserMessageStr = msg.content
                                            .map((p) => (isTextPart(p) ? p.text : ''))
                                            .join('');
                                    }
                                    else {
                                        firstUserMessageStr = msg.content;
                                    }
                                }
                            }
                        }
                    }
                    // Metadata update
                    metadata = {
                        ...metadata,
                        ...record.$set,
                    };
                }
                else if (isPartialMetadataRecord(record)) {
                    // Initial metadata line (or entire legacy record if on one line)
                    metadata = { ...metadata, ...record };
                    if (hasProperty(record, 'messages') &&
                        Array.isArray(record.messages)) {
                        for (const msg of record.messages) {
                            if (isMessageRecord(msg)) {
                                const id = msg.id;
                                const isUser = msg.type === 'user';
                                const isResumable = isResumableMessageRecord(msg);
                                if (options?.metadataOnly) {
                                    messageIds.push(id);
                                    messageKinds.set(id, {
                                        isUser,
                                        isResumable,
                                    });
                                }
                                else {
                                    messagesMap.set(id, msg);
                                }
                                if (!firstUserMessageStr &&
                                    isUser &&
                                    isResumable &&
                                    msg.content &&
                                    (Array.isArray(msg.content) ||
                                        typeof msg.content === 'string')) {
                                    if (Array.isArray(msg.content)) {
                                        firstUserMessageStr = msg.content
                                            .map((p) => (isTextPart(p) ? p.text : ''))
                                            .join('');
                                    }
                                    else {
                                        firstUserMessageStr = msg.content;
                                    }
                                }
                            }
                        }
                    }
                }
            }
            catch {
                // ignore parse errors on individual lines
            }
        }
        if (!metadata.sessionId || !metadata.projectHash) {
            return await parseLegacyRecordFallback(filePath, options);
        }
        const loadedMessages = Array.from(messagesMap.values());
        const metadataFirstUserMessage = loadedMessages.find((message) => message.type === 'user' && isResumableMessageRecord(message)) ?? null;
        let fallbackFirstUserMessage = firstUserMessageStr;
        if (!fallbackFirstUserMessage && metadataFirstUserMessage) {
            const rawContent = metadataFirstUserMessage.content;
            if (Array.isArray(rawContent)) {
                fallbackFirstUserMessage = rawContent
                    .map((part) => (isTextPart(part) ? part['text'] : ''))
                    .join('');
            }
            else if (typeof rawContent === 'string') {
                fallbackFirstUserMessage = rawContent;
            }
        }
        const userMessageCount = options?.metadataOnly
            ? Array.from(messageKinds.values()).filter((m) => m.isUser).length
            : loadedMessages.filter((m) => m.type === 'user').length;
        const hasResumableContent = options?.metadataOnly
            ? Array.from(messageKinds.values()).some((m) => m.isResumable)
            : hasResumableConversationContent(loadedMessages);
        return {
            sessionId: metadata.sessionId,
            projectHash: metadata.projectHash,
            startTime: metadata.startTime || new Date().toISOString(),
            lastUpdated: metadata.lastUpdated || new Date().toISOString(),
            summary: metadata.summary,
            memoryScratchpad: metadata.memoryScratchpad,
            directories: metadata.directories,
            kind: metadata.kind,
            messages: options?.metadataOnly ? [] : loadedMessages,
            messageCount: options?.metadataOnly
                ? loadedMessages.length || messageIds.length
                : loadedMessages.length,
            userMessageCount,
            memoryScratchpadIsStale: isTrackingMemoryScratchpadFreshness
                ? memoryScratchpadIsStale
                : undefined,
            firstUserMessage: fallbackFirstUserMessage,
            hasResumableContent,
        };
    }
    catch (error) {
        debugLogger.error('Error loading conversation record from JSONL:', error);
        return null;
    }
}
export class ChatRecordingService {
    conversationFile = null;
    cachedConversation = null;
    sessionId;
    projectHash;
    kind;
    queuedThoughts = [];
    queuedTokens = null;
    context;
    constructor(context) {
        this.context = context;
        this.sessionId = context.promptId;
        this.projectHash = getProjectHash(context.config.getProjectRoot());
    }
    async initialize(resumedSessionData, kind) {
        try {
            this.kind = kind;
            if (resumedSessionData) {
                this.conversationFile = resumedSessionData.filePath;
                this.sessionId = resumedSessionData.conversation.sessionId;
                this.kind = resumedSessionData.conversation.kind;
                const loadedRecord = await loadConversationRecord(this.conversationFile);
                if (loadedRecord) {
                    this.cachedConversation = loadedRecord;
                    this.projectHash = this.cachedConversation.projectHash;
                    if (this.conversationFile.endsWith('.json')) {
                        this.conversationFile = this.conversationFile + 'l'; // e.g. session-foo.jsonl
                        // Migrate the entire legacy record to the new file
                        const initialMetadata = {
                            sessionId: this.sessionId,
                            projectHash: this.projectHash,
                            startTime: this.cachedConversation.startTime,
                            lastUpdated: this.cachedConversation.lastUpdated,
                            kind: this.cachedConversation.kind,
                            directories: this.cachedConversation.directories,
                            summary: this.cachedConversation.summary,
                        };
                        this.appendRecord(initialMetadata);
                        for (const msg of this.cachedConversation.messages) {
                            this.appendRecord(msg);
                        }
                        if (this.cachedConversation.memoryScratchpad) {
                            this.appendRecord({
                                $set: {
                                    memoryScratchpad: this.cachedConversation.memoryScratchpad,
                                },
                            });
                        }
                    }
                    // Update the session ID in the existing file
                    this.updateMetadata({ sessionId: this.sessionId });
                }
                else {
                    throw new Error('Failed to load resumed session data from file');
                }
            }
            else {
                // Create new session
                this.sessionId = this.context.promptId;
                let chatsDir = path.join(this.context.config.storage.getProjectTempDir(), 'chats');
                // subagents are nested under the complete parent session id
                if (this.kind === 'subagent' && this.context.parentSessionId) {
                    const safeParentId = sanitizeFilenamePart(this.context.parentSessionId);
                    if (!safeParentId) {
                        throw new Error(`Invalid parentSessionId after sanitization: ${this.context.parentSessionId}`);
                    }
                    chatsDir = path.join(chatsDir, safeParentId);
                }
                fs.mkdirSync(chatsDir, { recursive: true });
                const timestamp = new Date()
                    .toISOString()
                    .slice(0, 16)
                    .replace(/:/g, '-');
                const safeSessionId = sanitizeFilenamePart(this.sessionId);
                if (!safeSessionId) {
                    throw new Error(`Invalid sessionId after sanitization: ${this.sessionId}`);
                }
                let filename;
                if (this.kind === 'subagent') {
                    filename = `${safeSessionId}.jsonl`;
                }
                else {
                    filename = `${SESSION_FILE_PREFIX}${timestamp}-${safeSessionId.slice(0, 8)}.jsonl`;
                }
                this.conversationFile = path.join(chatsDir, filename);
                const directories = this.kind === 'subagent'
                    ? [
                        ...(this.context.config
                            .getWorkspaceContext()
                            ?.getDirectories() ?? []),
                    ]
                    : undefined;
                const initialMetadata = {
                    sessionId: this.sessionId,
                    projectHash: this.projectHash,
                    startTime: new Date().toISOString(),
                    lastUpdated: new Date().toISOString(),
                    kind: this.kind,
                    directories,
                };
                this.appendRecord(initialMetadata);
                this.cachedConversation = {
                    ...initialMetadata,
                    messages: [],
                };
            }
            this.queuedThoughts = [];
            this.queuedTokens = null;
        }
        catch (error) {
            if (isNodeError(error) && error.code === 'ENOSPC') {
                this.conversationFile = null;
                debugLogger.warn(ENOSPC_WARNING_MESSAGE);
                return;
            }
            debugLogger.error('Error initializing chat recording service:', error);
            throw error;
        }
    }
    appendRecord(record) {
        if (!this.conversationFile)
            return;
        try {
            const line = JSON.stringify(record) + '\n';
            fs.mkdirSync(path.dirname(this.conversationFile), { recursive: true });
            fs.appendFileSync(this.conversationFile, line);
        }
        catch (error) {
            if (isNodeError(error) && error.code === 'ENOSPC') {
                this.conversationFile = null;
                debugLogger.warn(ENOSPC_WARNING_MESSAGE);
            }
            else {
                throw error;
            }
        }
    }
    updateMetadata(updates) {
        if (!this.cachedConversation)
            return;
        Object.assign(this.cachedConversation, updates);
        this.appendRecord({ $set: updates });
    }
    pushMessage(msg) {
        if (!this.cachedConversation)
            return;
        // We append the full message to the log
        this.appendRecord(msg);
        // Now update memory
        const index = this.cachedConversation.messages.findIndex((m) => m.id === msg.id);
        if (index !== -1) {
            this.cachedConversation.messages[index] = msg;
        }
        else {
            this.cachedConversation.messages.push(msg);
        }
    }
    getLastMessage(conversation) {
        return conversation.messages.at(-1);
    }
    newMessage(type, content, displayContent, id) {
        return {
            id: id || randomUUID(),
            timestamp: new Date().toISOString(),
            type,
            content,
            displayContent,
        };
    }
    recordMessage(message) {
        if (!this.conversationFile || !this.cachedConversation)
            return message.id || randomUUID();
        try {
            const msg = this.newMessage(message.type, message.content, message.displayContent, message.id);
            if (msg.type === 'gemini') {
                msg.thoughts = this.queuedThoughts;
                msg.tokens = this.queuedTokens;
                msg.model = message.model;
                this.queuedThoughts = [];
                this.queuedTokens = null;
            }
            this.pushMessage(msg);
            this.updateMetadata({ lastUpdated: new Date().toISOString() });
            return msg.id;
        }
        catch (error) {
            debugLogger.error('Error saving message to chat history.', error);
            throw error;
        }
    }
    /**
     * Records a synthetic message (e.g. Binary Received, Snapshot/Summary)
     * and returns its durable ID.
     */
    recordSyntheticMessage(type, content, id) {
        return this.recordMessage({
            model: undefined,
            type,
            content,
            id,
        });
    }
    recordThought(thought) {
        if (!this.conversationFile)
            return;
        this.queuedThoughts.push({
            ...thought,
            timestamp: new Date().toISOString(),
        });
    }
    recordMessageTokens(respUsageMetadata) {
        if (!this.conversationFile || !this.cachedConversation)
            return;
        try {
            const tokens = {
                input: respUsageMetadata.promptTokenCount ?? 0,
                output: respUsageMetadata.candidatesTokenCount ?? 0,
                cached: respUsageMetadata.cachedContentTokenCount ?? 0,
                thoughts: respUsageMetadata.thoughtsTokenCount ?? 0,
                tool: respUsageMetadata.toolUsePromptTokenCount ?? 0,
                total: respUsageMetadata.totalTokenCount ?? 0,
            };
            const lastMsg = this.getLastMessage(this.cachedConversation);
            if (lastMsg && lastMsg.type === 'gemini' && !lastMsg.tokens) {
                lastMsg.tokens = tokens;
                this.queuedTokens = null;
                this.pushMessage(lastMsg);
            }
            else {
                this.queuedTokens = tokens;
            }
        }
        catch (error) {
            debugLogger.error('Error updating message tokens in chat history.', error);
            throw error;
        }
    }
    recordToolCalls(model, toolCalls) {
        if (!this.conversationFile || !this.cachedConversation)
            return;
        const toolRegistry = this.context.toolRegistry;
        const enrichedToolCalls = toolCalls.map((toolCall) => {
            const toolInstance = toolRegistry.getTool(toolCall.name);
            return {
                ...toolCall,
                displayName: toolInstance?.displayName || toolCall.name,
                description: toolCall.description?.trim() || toolInstance?.description || '',
                renderOutputAsMarkdown: toolInstance?.isOutputMarkdown || false,
            };
        });
        try {
            const lastMsg = this.getLastMessage(this.cachedConversation);
            if (!lastMsg ||
                lastMsg.type !== 'gemini' ||
                this.queuedThoughts.length > 0) {
                const newMsg = {
                    ...this.newMessage('gemini', ''),
                    type: 'gemini',
                    toolCalls: enrichedToolCalls,
                    thoughts: this.queuedThoughts,
                    model,
                };
                if (this.queuedThoughts.length > 0) {
                    newMsg.thoughts = this.queuedThoughts;
                    this.queuedThoughts = [];
                }
                if (this.queuedTokens) {
                    newMsg.tokens = this.queuedTokens;
                    this.queuedTokens = null;
                }
                this.pushMessage(newMsg);
            }
            else {
                if (!lastMsg.toolCalls) {
                    lastMsg.toolCalls = [];
                }
                // Deep clone toolCalls to avoid modifying memory references directly
                const updatedToolCalls = [...lastMsg.toolCalls];
                for (const toolCall of enrichedToolCalls) {
                    const index = updatedToolCalls.findIndex((tc) => tc.id === toolCall.id);
                    if (index !== -1) {
                        updatedToolCalls[index] = {
                            ...updatedToolCalls[index],
                            ...toolCall,
                        };
                    }
                    else {
                        updatedToolCalls.push(toolCall);
                    }
                }
                lastMsg.toolCalls = updatedToolCalls;
                this.pushMessage(lastMsg);
            }
        }
        catch (error) {
            debugLogger.error('Error adding tool call to message in chat history.', error);
            throw error;
        }
    }
    saveSummary(summary) {
        if (!this.conversationFile)
            return;
        try {
            this.updateMetadata({ summary });
        }
        catch (error) {
            debugLogger.error('Error saving summary to chat history.', error);
        }
    }
    recordDirectories(directories) {
        if (!this.conversationFile)
            return;
        try {
            this.updateMetadata({ directories: [...directories] });
        }
        catch (error) {
            debugLogger.error('Error saving directories to chat history.', error);
        }
    }
    getConversation() {
        if (!this.conversationFile)
            return null;
        return this.cachedConversation;
    }
    getConversationFilePath() {
        return this.conversationFile;
    }
    /**
     * Deletes a session file by sessionId, filename, or basename.
     * Derives an 8-character shortId to find and delete all associated files
     * (parent and subagents).
     *
     * @throws {Error} If shortId validation fails.
     */
    async deleteSession(sessionIdOrBasename) {
        return deleteStoredSession(this.context.config, sessionIdOrBasename);
    }
    /**
     * Asynchronously deletes the current session's chat file and tool outputs.
     * This encapsulates the session ID logic and uses non-blocking I/O to avoid
     * blocking the event loop on exit.
     */
    async deleteCurrentSessionAsync() {
        if (!this.conversationFile) {
            return;
        }
        try {
            const tempDir = this.context.config.storage.getProjectTempDir();
            // Delete the conversation file directly using the tracked path.
            await fs.promises.unlink(this.conversationFile).catch(() => {
                // File may not exist; ignore.
            });
            // Delegate tool-output and log cleanup to the shared utility.
            await deleteSessionArtifactsAsync(this.sessionId, tempDir);
        }
        catch (error) {
            debugLogger.error('Error deleting current session.', error);
            throw error;
        }
    }
    /**
     * Deletes the current session only if it has no resumable conversation
     * content. This removes abandoned startup-only sessions while preserving any
     * session with a real user prompt, model response, or tool activity.
     */
    async deleteCurrentSessionIfNotResumableAsync() {
        if (!this.conversationFile || !this.cachedConversation) {
            return;
        }
        if (hasResumableConversationContent(this.cachedConversation.messages)) {
            return;
        }
        await this.deleteCurrentSessionAsync();
    }
    /**
     * Rewinds the conversation to the state just before the specified message ID.
     * All messages from (and including) the specified ID onwards are removed.
     */
    rewindTo(messageId) {
        if (!this.conversationFile || !this.cachedConversation)
            return null;
        const messageIndex = this.cachedConversation.messages.findIndex((m) => m.id === messageId);
        if (messageIndex === -1) {
            debugLogger.error('Message to rewind to not found in conversation history');
            return this.cachedConversation;
        }
        this.cachedConversation.messages = this.cachedConversation.messages.slice(0, messageIndex);
        this.appendRecord({ $rewindTo: messageId });
        return this.cachedConversation;
    }
    updateMessagesFromHistory(history) {
        if (!this.conversationFile || !this.cachedConversation)
            return;
        try {
            let updated = false;
            // 1. Sync content and IDs
            const newMessages = history.map((turn) => {
                const existing = this.cachedConversation?.messages.find((m) => m.id === turn.id);
                if (existing) {
                    // If content parts have changed (e.g. masking), update them
                    if (JSON.stringify(existing.content) !==
                        JSON.stringify(turn.content.parts)) {
                        updated = true;
                    }
                    return {
                        ...existing,
                        content: turn.content.parts || [],
                    };
                }
                // It's a new (possibly synthetic) turn like a summary
                updated = true;
                return this.newMessage(turn.content.role === 'user' ? 'user' : 'gemini', turn.content.parts || [], undefined, turn.id);
            });
            // 2. Specialized 'Masking Sync' for tool call results
            // If a user turn in history contains a functionResponse, we update the
            // corresponding ToolCallRecord in the preceding gemini message.
            for (const turn of history) {
                if (turn.content.role !== 'user')
                    continue;
                for (const part of turn.content.parts || []) {
                    if (part.functionResponse) {
                        const callId = part.functionResponse.id;
                        // Find the gemini message that contains this tool call
                        const geminiMsg = newMessages.find((m) => m.type === 'gemini' &&
                            m.toolCalls?.some((tc) => tc.id === callId));
                        if (geminiMsg && geminiMsg.type === 'gemini') {
                            const tc = geminiMsg.toolCalls.find((tc) => tc.id === callId);
                            if (tc) {
                                // If the history version is different (e.g. masked), sync it into the record
                                // We sync the entire parts array of the user turn to ensure sibling parts are preserved
                                if (JSON.stringify(tc.result) !==
                                    JSON.stringify(turn.content.parts)) {
                                    tc.result = turn.content.parts || [];
                                    updated = true;
                                }
                            }
                        }
                    }
                }
            }
            if (updated ||
                newMessages.length !== this.cachedConversation.messages.length) {
                this.cachedConversation.messages = newMessages;
                this.updateMetadata({
                    messages: newMessages,
                    lastUpdated: new Date().toISOString(),
                });
            }
        }
        catch (error) {
            debugLogger.error('Error updating conversation history from memory.', error);
            throw error;
        }
    }
}
async function parseLegacyRecordFallback(filePath, options) {
    try {
        const fileContent = await fs.promises.readFile(filePath, 'utf8');
        const parsed = JSON.parse(fileContent);
        const isLegacyRecord = (val) => typeof val === 'object' && val !== null && 'sessionId' in val;
        if (isLegacyRecord(parsed)) {
            const legacyRecord = parsed;
            if (options?.metadataOnly) {
                let fallbackFirstUserMessageStr;
                const firstUserMessage = legacyRecord.messages?.find((m) => m.type === 'user' && isResumableMessageRecord(m));
                if (firstUserMessage) {
                    const rawContent = firstUserMessage.content;
                    if (Array.isArray(rawContent)) {
                        fallbackFirstUserMessageStr = rawContent
                            .map((p) => (isTextPart(p) ? p['text'] : ''))
                            .join('');
                    }
                    else if (typeof rawContent === 'string') {
                        fallbackFirstUserMessageStr = rawContent;
                    }
                }
                return {
                    ...legacyRecord,
                    messages: [],
                    messageCount: legacyRecord.messages?.length || 0,
                    userMessageCount: legacyRecord.messages?.filter((m) => m.type === 'user').length || 0,
                    firstUserMessage: fallbackFirstUserMessageStr,
                    hasResumableContent: legacyRecord.messages?.some((m) => isResumableMessageRecord(m)) ||
                        false,
                };
            }
            return {
                ...legacyRecord,
                userMessageCount: legacyRecord.messages?.filter((m) => m.type === 'user').length || 0,
                hasResumableContent: legacyRecord.messages?.some((m) => isResumableMessageRecord(m)) ||
                    false,
            };
        }
    }
    catch {
        // ignore legacy fallback parse error
    }
    return null;
}
//# sourceMappingURL=chatRecordingService.js.map