/**
 * Owns durable presentation metadata extracted from a completed turn trace.
 *
 * This is not model transcript history and it is not host UI rendering. Core
 * owns this compact, stable activity contract so terminal and browser clients
 * can reconstruct the same conversation-adjacent tool activity without parsing
 * raw trace files or duplicating tool payload semantics.
 */
import dayjs from 'dayjs';
import compact from 'lodash/compact.js';
import isPlainObject from 'lodash/isPlainObject.js';
import isString from 'lodash/isString.js';
import orderBy from 'lodash/orderBy.js';
import { HeddleEventType } from '../../../../../core/event-types.js';
import { ToolActivitySummarizer } from '../../../../../core/live/index.js';
import { ConversationTurnPresentationSchema } from './schema.js';
export class ConversationTurnPresentationService {
    static project(input) {
        const requestedApprovals = input.trace
            .filter((event) => (event.type === HeddleEventType.toolApprovalRequested));
        const approvalResolutions = new Map(input.trace
            .filter((event) => (event.type === HeddleEventType.toolApprovalResolved))
            .map((event) => [event.call.id, event]));
        const approvals = requestedApprovals.map((event) => (ConversationTurnPresentationService.projectApproval(input.turnId, event, approvalResolutions.get(event.call.id))));
        const editDiffs = compact(input.trace.map((event) => (event.type === HeddleEventType.toolCompleted
            ? ConversationTurnPresentationService.projectEditDiff(input.turnId, event)
            : undefined)));
        const timelineItems = ConversationTurnPresentationService.sortTimelineItems([...approvals, ...editDiffs]);
        return timelineItems.length > 0 ? { timelineItems } : undefined;
    }
    static read(raw) {
        const parsed = ConversationTurnPresentationSchema.safeParse(raw);
        return parsed.success ? parsed.data : undefined;
    }
    static projectApproval(turnId, event, resolution) {
        const command = ConversationTurnPresentationService.readCommandOrPath(event.call);
        return {
            type: 'approval',
            id: `${turnId}:approval:${event.call.id}`,
            toolCallId: event.call.id,
            tool: event.call.tool,
            summary: ToolActivitySummarizer.summarizeCall(event.call),
            status: resolution ? (resolution.approved ? 'approved' : 'denied')
                : 'requested',
            ...(command ? { command } : {}),
            ...(resolution?.reason ? { reason: resolution.reason } : {}),
            step: event.step,
            timestamp: resolution?.timestamp ?? event.timestamp,
        };
    }
    static projectEditDiff(turnId, event) {
        if (event.call.tool !== 'edit_file' || event.result.ok !== true || !isPlainObject(event.result.output)) {
            return undefined;
        }
        const output = event.result.output;
        const diff = isPlainObject(output.diff) ? output.diff : undefined;
        const path = ConversationTurnPresentationService.readNonEmptyString(output.path);
        const patch = ConversationTurnPresentationService.readNonEmptyString(diff?.diff);
        if (!path || !patch) {
            return undefined;
        }
        const action = ConversationTurnPresentationService.readNonEmptyString(output.action);
        return {
            type: 'edit_diff',
            id: `${turnId}:edit-diff:${event.call.id}`,
            toolCallId: event.call.id,
            path,
            ...(action ? { action } : {}),
            patch,
            truncated: diff?.truncated === true,
            step: event.step,
            timestamp: event.timestamp,
        };
    }
    static sortTimelineItems(timelineItems) {
        return orderBy(timelineItems, [
            (item) => dayjs(item.timestamp).valueOf(),
            (item) => item.step ?? Number.MAX_SAFE_INTEGER,
            'id',
        ], ['asc', 'asc', 'asc']);
    }
    static readCommandOrPath(call) {
        if (!isPlainObject(call.input)) {
            return undefined;
        }
        const input = call.input;
        return ConversationTurnPresentationService.readNonEmptyString(input.command)
            ?? ConversationTurnPresentationService.readNonEmptyString(input.path);
    }
    static readNonEmptyString(value) {
        return isString(value) && value.trim().length > 0 ? value.trim() : undefined;
    }
}
//# sourceMappingURL=turn-presentation-service.js.map