/**
 * @license
 * Copyright 2026 HopCode Team Code
 * SPDX-License-Identifier: Apache-2.0
 */
import * as path from 'node:path';
import { ToolCallStatus } from '../types.js';
/**
 * Extracts text content from a Content object's parts (excluding thought parts).
 */
function extractTextFromParts(parts) {
    if (!parts)
        return '';
    const textParts = [];
    for (const part of parts) {
        if ('text' in part && part.text) {
            // Skip thought parts - they have a 'thought' property
            if (!('thought' in part && part.thought)) {
                textParts.push(part.text);
            }
        }
    }
    return textParts.join('\n');
}
/**
 * Extracts thought text content from a Content object's parts.
 * Thought parts are identified by having `thought: true`.
 */
function extractThoughtTextFromParts(parts) {
    if (!parts)
        return '';
    const thoughtParts = [];
    for (const part of parts) {
        if ('text' in part && part.text && 'thought' in part && part.thought) {
            thoughtParts.push(part.text);
        }
    }
    return thoughtParts.join('\n');
}
/**
 * Extracts function calls from a Content object's parts.
 */
function extractFunctionCalls(parts) {
    if (!parts)
        return [];
    const calls = [];
    for (const part of parts) {
        if ('functionCall' in part && part.functionCall) {
            const fc = part.functionCall;
            calls.push({
                id: fc.id || `call-${calls.length}`,
                name: fc.name || 'unknown',
                args: fc.args || {},
            });
        }
    }
    return calls;
}
function getTool(config, name) {
    if (!config)
        return undefined;
    const toolRegistry = config.getToolRegistry();
    return toolRegistry.getTool(name);
}
/**
 * Formats a tool description from its name and arguments using actual tool instances.
 * This ensures we get the exact same descriptions as during normal operation.
 */
function formatToolDescription(tool, args) {
    try {
        // Create tool invocation instance and get description
        const invocation = tool.build(args);
        return invocation.getDescription();
    }
    catch {
        // Fallback: use the description arg directly if available
        if (typeof args['description'] === 'string') {
            return args['description'];
        }
        return '';
    }
}
/**
 * Restores a HistoryItemWithoutId from the serialized shape stored in
 * SlashCommandRecordPayload.outputHistoryItems.
 */
function restoreHistoryItem(raw) {
    if (!raw || typeof raw !== 'object') {
        return;
    }
    const clone = { ...raw };
    if ('timestamp' in clone) {
        const ts = clone['timestamp'];
        if (typeof ts === 'string' || typeof ts === 'number') {
            clone['timestamp'] = new Date(ts);
        }
    }
    if (typeof clone['type'] !== 'string') {
        return;
    }
    return clone;
}
/**
 * Converts ChatRecord messages to UI history items for display.
 *
 * This function transforms the raw ChatRecords into a format suitable
 * for the CLI's HistoryItemDisplay component.
 *
 * @param conversation The conversation record from a resumed session
 * @param config The config object for accessing tool registry
 * @returns Array of history items for UI display
 */
function convertToHistoryItems(conversation, config) {
    const items = [];
    const pendingAtCommands = [];
    let atCommandCounter = 0;
    // Track pending tool calls for grouping with results
    const pendingToolCalls = new Map();
    let currentToolGroup = [];
    const buildAtCommandDisplays = (payload) => {
        // Error case: single "Read File(s)" with error message
        if (payload.status === 'error') {
            atCommandCounter += 1;
            const filesLabel = payload.filesRead?.length
                ? payload.filesRead.join(', ')
                : 'files';
            return [
                {
                    callId: `at-command-${atCommandCounter}`,
                    name: 'Read File(s)',
                    description: 'Error attempting to read files',
                    status: ToolCallStatus.Error,
                    resultDisplay: payload.message || `Error reading files (${filesLabel})`,
                    confirmationDetails: undefined,
                },
            ];
        }
        // Success case: individual tool calls for each file
        if (!payload.filesRead?.length) {
            atCommandCounter += 1;
            return [
                {
                    callId: `at-command-${atCommandCounter}`,
                    name: 'Read File',
                    description: 'Read File(s)',
                    status: ToolCallStatus.Success,
                    resultDisplay: undefined,
                    confirmationDetails: undefined,
                },
            ];
        }
        return payload.filesRead.map((filePath) => {
            atCommandCounter += 1;
            const isDir = filePath.endsWith('/');
            return {
                callId: `at-command-${atCommandCounter}`,
                name: isDir ? 'Read Directory' : 'Read File',
                description: isDir
                    ? `Read directory ${path.basename(filePath)}`
                    : `Read file ${path.basename(filePath)}`,
                status: ToolCallStatus.Success,
                resultDisplay: undefined,
                confirmationDetails: undefined,
            };
        });
    };
    for (const record of conversation.messages) {
        if (record.type === 'system') {
            if (record.subtype === 'slash_command') {
                // Flush any pending tool group to avoid mixing contexts.
                if (currentToolGroup.length > 0) {
                    items.push({
                        type: 'tool_group',
                        tools: [...currentToolGroup],
                    });
                    currentToolGroup = [];
                }
                const payload = record.systemPayload;
                if (!payload)
                    continue;
                if (payload.phase === 'invocation' && payload.rawCommand) {
                    items.push({ type: 'user', text: payload.rawCommand });
                }
                if (payload.phase === 'result') {
                    const outputs = payload.outputHistoryItems ?? [];
                    for (const raw of outputs) {
                        const restored = restoreHistoryItem(raw);
                        if (restored) {
                            items.push(restored);
                        }
                    }
                }
            }
            if (record.subtype === 'at_command') {
                const payload = record.systemPayload;
                if (!payload)
                    continue;
                pendingAtCommands.push(payload);
            }
            if (record.subtype === 'rewind') {
                items.push({ type: 'info', text: 'Conversation rewound.' });
            }
            continue;
        }
        switch (record.type) {
            case 'user': {
                // Restore notification items (background agent completions and cron fires)
                if (record.subtype === 'notification' || record.subtype === 'cron') {
                    const payload = record.systemPayload;
                    const fallback = record.subtype === 'cron'
                        ? 'Cron job fired'
                        : 'Background agent completed';
                    const text = payload?.displayText ||
                        extractTextFromParts(record.message?.parts) ||
                        fallback;
                    items.push({ type: 'notification', text });
                    break;
                }
                if (pendingAtCommands.length > 0) {
                    // Flush any pending tool group before user message
                    if (currentToolGroup.length > 0) {
                        items.push({
                            type: 'tool_group',
                            tools: [...currentToolGroup],
                        });
                        currentToolGroup = [];
                    }
                    const payload = pendingAtCommands.shift();
                    const text = payload.userText ||
                        extractTextFromParts(record.message?.parts);
                    if (text) {
                        items.push({ type: 'user', text });
                    }
                    const toolDisplays = buildAtCommandDisplays(payload);
                    if (toolDisplays.length > 0) {
                        items.push({
                            type: 'tool_group',
                            tools: toolDisplays,
                        });
                    }
                    break;
                }
                // Flush any pending tool group before user message
                if (currentToolGroup.length > 0) {
                    items.push({
                        type: 'tool_group',
                        tools: [...currentToolGroup],
                    });
                    currentToolGroup = [];
                }
                const text = extractTextFromParts(record.message?.parts);
                if (text) {
                    items.push({ type: 'user', text });
                }
                break;
            }
            case 'assistant': {
                const parts = record.message?.parts;
                // Extract thought content. With no config (standalone picker preview),
                // default to showing thoughts verbatim (same path as
                // `!useSummarizedThinking()`).
                const thoughtText = !config || !config.getContentGenerator().useSummarizedThinking()
                    ? extractThoughtTextFromParts(parts)
                    : '';
                // Extract text content (non-function-call, non-thought)
                const text = extractTextFromParts(parts);
                // Extract function calls
                const functionCalls = extractFunctionCalls(parts);
                // If there's thought content, add it as a gemini_thought message
                if (thoughtText) {
                    // Flush any pending tool group before thought
                    if (currentToolGroup.length > 0) {
                        items.push({
                            type: 'tool_group',
                            tools: [...currentToolGroup],
                        });
                        currentToolGroup = [];
                    }
                    items.push({ type: 'gemini_thought', text: thoughtText });
                }
                // If there's text content, add it as a gemini message
                if (text) {
                    // Flush any pending tool group before text
                    if (currentToolGroup.length > 0) {
                        items.push({
                            type: 'tool_group',
                            tools: [...currentToolGroup],
                        });
                        currentToolGroup = [];
                    }
                    items.push({ type: 'gemini', text });
                }
                // Track function calls for pairing with results
                for (const fc of functionCalls) {
                    const tool = getTool(config, fc.name);
                    pendingToolCalls.set(fc.id, { name: fc.name, args: fc.args });
                    // Add placeholder tool call to current group
                    currentToolGroup.push({
                        callId: fc.id,
                        name: tool?.displayName || fc.name,
                        description: tool ? formatToolDescription(tool, fc.args) : '',
                        resultDisplay: undefined,
                        status: ToolCallStatus.Success, // Will be updated by tool_result
                        confirmationDetails: undefined,
                    });
                }
                break;
            }
            case 'tool_result': {
                // Update the corresponding tool call in the current group
                if (record.toolCallResult) {
                    const callId = record.toolCallResult.callId;
                    const toolCall = currentToolGroup.find((t) => t.callId === callId);
                    if (toolCall) {
                        // Preserve the resultDisplay as-is - it can be a string or structured object
                        const rawDisplay = record.toolCallResult.resultDisplay;
                        toolCall.resultDisplay = rawDisplay;
                        // Check if status exists and use it
                        const rawStatus = record.toolCallResult['status'];
                        toolCall.status =
                            rawStatus === 'error'
                                ? ToolCallStatus.Error
                                : ToolCallStatus.Success;
                    }
                    pendingToolCalls.delete(callId || '');
                }
                break;
            }
            default:
                // Skip unknown record types
                break;
        }
    }
    if (pendingAtCommands.length > 0) {
        for (const payload of pendingAtCommands) {
            // Flush any pending tool group before standalone @-command
            if (currentToolGroup.length > 0) {
                items.push({
                    type: 'tool_group',
                    tools: [...currentToolGroup],
                });
                currentToolGroup = [];
            }
            const text = payload.userText;
            if (text) {
                items.push({ type: 'user', text });
            }
            const toolDisplays = buildAtCommandDisplays(payload);
            if (toolDisplays.length > 0) {
                items.push({
                    type: 'tool_group',
                    tools: toolDisplays,
                });
            }
        }
    }
    // Flush any remaining tool group
    if (currentToolGroup.length > 0) {
        items.push({
            type: 'tool_group',
            tools: currentToolGroup,
        });
    }
    return items;
}
/**
 * Builds the complete UI history items for a resumed session.
 *
 * This function takes the resumed session data, converts it to UI history format,
 * and assigns unique IDs to each item for use with loadHistory.
 *
 * @param sessionData The resumed session data from SessionService
 * @param config The config object for accessing tool registry. Pass `null`
 *   to render in "preview" mode (no tool metadata lookup, thoughts shown
 *   verbatim) — used by the standalone resume picker that runs before
 *   `loadCliConfig`.
 * @param baseTimestamp Base timestamp for generating unique IDs
 * @returns Array of HistoryItem with proper IDs
 */
export function buildResumedHistoryItems(sessionData, config, baseTimestamp = Date.now()) {
    const items = [];
    let idCounter = 1;
    const getNextId = () => baseTimestamp + idCounter++;
    // Convert conversation directly to history items
    const historyItems = convertToHistoryItems(sessionData.conversation, config);
    for (const item of historyItems) {
        items.push({
            ...item,
            id: getNextId(),
        });
    }
    return items;
}
//# sourceMappingURL=resumeHistoryUtils.js.map