/**
 * Pure data transformations for session HTML export.
 *
 * This module takes raw session entries and produces view models
 * consumed by template.js. All functions are pure — no DOM, no side effects.
 *
 * The boundary: session-schema.ts defines the JSONL schema (SessionEntry union),
 * this module transforms it into renderable structures, and template.js handles DOM.
 */
/** Cast helper for entries that may include unknown types (custom, session_info, etc.) */
export function asEntry(raw) {
    return raw;
}
function isMessage(entry) {
    return entry.type === 'message';
}
/** All entry type discriminators that the renderer knows about. */
export const KNOWN_ENTRY_TYPES = [
    'message',
    'turn_start',
    'turn_end',
    'system_prompt',
    'model_change',
    'thinking_level_change',
    'compaction',
    'custom_message',
    'label',
    'custom',
    'session_info',
    'tool_definitions',
    'tool_execution_end',
    'llm_call'
];
/** Build all indexes from raw entries. O(n) per index. */
export function buildIndexes(entries) {
    const byId = new Map();
    for (const entry of entries) {
        byId.set(entry.id, entry);
    }
    const toolCallMap = new Map();
    for (const entry of entries) {
        if (isMessage(entry) && entry.message.role === 'assistant') {
            const content = entry.message.content;
            if (Array.isArray(content)) {
                for (const block of content) {
                    if (block.type === 'toolCall') {
                        toolCallMap.set(block.id, { name: block.name, arguments: block.arguments });
                    }
                }
            }
        }
    }
    const labelMap = new Map();
    for (const entry of entries) {
        if (entry.type === 'label' && entry.targetId && entry.label) {
            labelMap.set(entry.targetId, entry.label);
        }
    }
    // Build turn_start → turn_end map, then map first-message-after-turn → turn info
    const turnEndByStartId = new Map();
    for (const entry of entries) {
        if (entry.type === 'turn_end') {
            let current = byId.get(entry.parentId ?? '');
            while (current) {
                if (current.type === 'turn_start') {
                    turnEndByStartId.set(current.id, entry);
                    break;
                }
                if (!current.parentId || current.parentId === current.id)
                    break;
                current = byId.get(current.parentId);
            }
        }
    }
    const turnInfoByMessageId = new Map();
    for (const entry of entries) {
        if (entry.type === 'turn_start') {
            for (const child of entries) {
                if (child.parentId === entry.id && child.type === 'message') {
                    const turnEnd = turnEndByStartId.get(entry.id);
                    turnInfoByMessageId.set(child.id, {
                        turnIndex: entry.turnIndex,
                        durationMs: turnEnd?.durationMs
                    });
                    break;
                }
            }
        }
    }
    return { byId, toolCallMap, labelMap, turnInfoByMessageId };
}
/** Build tree from flat entries. */
export function buildTree(entries, labelMap) {
    const nodeMap = new Map();
    const roots = [];
    for (const entry of entries) {
        nodeMap.set(entry.id, {
            entry,
            children: [],
            label: labelMap.get(entry.id)
        });
    }
    for (const entry of entries) {
        const node = nodeMap.get(entry.id);
        if (entry.parentId === null || entry.parentId === undefined || entry.parentId === entry.id) {
            roots.push(node);
        }
        else {
            const parent = nodeMap.get(entry.parentId);
            if (parent) {
                parent.children.push(node);
            }
            else {
                roots.push(node);
            }
        }
    }
    function sortChildren(node) {
        node.children.sort((a, b) => new Date(a.entry.timestamp).getTime() - new Date(b.entry.timestamp).getTime());
        node.children.forEach(sortChildren);
    }
    roots.forEach(sortChildren);
    return roots;
}
// ============================================================
// PATH
// ============================================================
/** Get path from root to target (the conversation thread). */
export function getPath(targetId, byId) {
    const path = [];
    let current = byId.get(targetId);
    while (current) {
        path.unshift(current);
        if (!current.parentId || current.parentId === current.id)
            break;
        current = byId.get(current.parentId);
    }
    return path;
}
/** Build set of entry IDs on path from root to target. */
export function buildActivePathIds(targetId, byId) {
    const ids = new Set();
    let current = byId.get(targetId);
    while (current) {
        ids.add(current.id);
        if (!current.parentId || current.parentId === current.id)
            break;
        current = byId.get(current.parentId);
    }
    return ids;
}
function mapToRecord(map) {
    return Object.fromEntries(map.entries());
}
/** Find the tool result entry matching a tool call ID. */
export function findToolResult(toolCallId, entries) {
    for (const entry of entries) {
        if (isMessage(entry) && entry.message.role === 'toolResult') {
            if (entry.message.toolCallId === toolCallId) {
                return {
                    content: entry.message.content,
                    isError: entry.message.isError || false,
                    details: entry.message.details
                };
            }
        }
    }
    return null;
}
export function buildToolResultMap(entries) {
    const toolResultsByCallId = new Map();
    for (const entry of entries) {
        if (isMessage(entry) && entry.message.role === 'toolResult' && entry.message.toolCallId) {
            toolResultsByCallId.set(entry.message.toolCallId, {
                content: entry.message.content,
                isError: entry.message.isError || false,
                details: entry.message.details
            });
        }
    }
    return toolResultsByCallId;
}
/** Compute aggregate stats from a list of entries. */
export function computeStats(entryList) {
    let userMessages = 0;
    let assistantMessages = 0;
    let toolResults = 0;
    let customMessages = 0;
    let compactions = 0;
    let toolCalls = 0;
    let turnCount = 0;
    let totalDurationMs = 0;
    let totalLlmDurationMs = 0;
    let totalToolDurationMs = 0;
    const tokens = { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 };
    const cost = { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 };
    const models = new Set();
    for (const entry of entryList) {
        if (entry.type === 'turn_end') {
            turnCount++;
            if (typeof entry.durationMs === 'number')
                totalDurationMs += entry.durationMs;
        }
        if (isMessage(entry)) {
            const msg = entry.message;
            if (msg.role === 'user')
                userMessages++;
            if (msg.role === 'assistant') {
                assistantMessages++;
                if (msg.model) {
                    models.add(msg.provider ? `${msg.provider}/${msg.model}` : String(msg.model));
                }
                const usage = msg.usage;
                if (usage) {
                    tokens.input += usage.input || 0;
                    tokens.output += usage.output || 0;
                    tokens.cacheRead += usage.cacheRead || 0;
                    tokens.cacheWrite += usage.cacheWrite || 0;
                    const usageCost = usage.cost;
                    if (usageCost) {
                        cost.input += usageCost.input || 0;
                        cost.output += usageCost.output || 0;
                        cost.cacheRead += usageCost.cacheRead || 0;
                        cost.cacheWrite += usageCost.cacheWrite || 0;
                    }
                }
                const content = msg.content;
                if (Array.isArray(content)) {
                    toolCalls += content.filter((c) => c.type === 'toolCall').length;
                }
                if (typeof entry.durationMs === 'number')
                    totalLlmDurationMs += entry.durationMs;
            }
            if (msg.role === 'toolResult') {
                toolResults++;
                if (typeof entry.durationMs === 'number')
                    totalToolDurationMs += entry.durationMs;
            }
        }
        else if (entry.type === 'compaction') {
            compactions++;
        }
        else if (entry.type === 'custom_message') {
            customMessages++;
        }
    }
    return {
        userMessages,
        assistantMessages,
        toolResults,
        customMessages,
        compactions,
        toolCalls,
        turnCount,
        totalDurationMs,
        totalLlmDurationMs,
        totalToolDurationMs,
        tokens,
        cost,
        models: Array.from(models)
    };
}
export function buildSessionExportViewModel(entries) {
    const indexes = buildIndexes(entries);
    return {
        tree: buildTree(entries, indexes.labelMap),
        toolCallsById: mapToRecord(indexes.toolCallMap),
        turnInfoByMessageId: mapToRecord(indexes.turnInfoByMessageId),
        toolResultsByCallId: mapToRecord(buildToolResultMap(entries)),
        stats: computeStats(entries)
    };
}
// ============================================================
// FORMATTING UTILITIES
// ============================================================
export function formatTokens(count) {
    if (count >= 1_000_000)
        return (count / 1_000_000).toFixed(1) + 'M';
    if (count >= 1_000)
        return (count / 1_000).toFixed(1) + 'k';
    return String(count);
}
export function formatDuration(ms) {
    if (ms < 1000)
        return ms + 'ms';
    const s = ms / 1000;
    if (s < 60)
        return s.toFixed(1) + 's';
    const m = Math.floor(s / 60);
    const rem = Math.round(s % 60);
    return m + 'm' + (rem > 0 ? rem + 's' : '');
}
export function formatTimestamp(ts) {
    if (!ts)
        return '';
    try {
        return new Date(ts).toLocaleTimeString();
    }
    catch {
        return '';
    }
}
/** Entry types considered "settings" — hidden in default filter mode. */
const SETTINGS_TYPES = new Set([
    'label',
    'custom',
    'custom_message',
    'model_change',
    'thinking_level_change',
    'turn_start',
    'turn_end',
    'system_prompt',
    'tool_definitions',
    'tool_execution_end'
]);
export function hasTextContent(content) {
    if (typeof content === 'string')
        return content.trim().length > 0;
    if (Array.isArray(content)) {
        for (const c of content) {
            if (c.type === 'text' && c.text && c.text.trim().length > 0)
                return true;
        }
    }
    return false;
}
export function extractContent(content) {
    if (typeof content === 'string')
        return content;
    if (Array.isArray(content)) {
        return content
            .filter((c) => c.type === 'text' && c.text)
            .map((c) => c.text)
            .join('');
    }
    return '';
}
/** Test whether an entry passes the current filter. */
export function passesFilter(item, mode, currentLeafId) {
    const { entry, label } = item;
    if (entry.type === 'custom_message' && !entry.display)
        return false;
    // Always show current leaf
    if (entry.id === currentLeafId)
        return true;
    // Hide assistant messages with only tool calls (no text) unless error/aborted
    if (isMessage(entry) && entry.message.role === 'assistant') {
        const msg = entry.message;
        const hasText = hasTextContent(msg.content);
        const isErrorOrAborted = msg.stopReason && msg.stopReason !== 'stop' && msg.stopReason !== 'toolUse';
        if (!hasText && !isErrorOrAborted)
            return false;
    }
    const isSettings = SETTINGS_TYPES.has(entry.type);
    switch (mode) {
        case 'user-only':
            return isMessage(entry) && entry.message.role === 'user';
        case 'no-tools':
            return !isSettings && !(isMessage(entry) && entry.message.role === 'toolResult');
        case 'labeled-only':
            return label !== undefined;
        case 'all':
            return true;
        default: // 'default'
            return !isSettings;
    }
}
//# sourceMappingURL=session-view-model.js.map