/**
 * OpenCode Event Mapper
 *
 * Maps OpenCode SSE events to the provider-agnostic AgentMessage interface.
 * Handles deduplication of tool_use/tool_result events and text delta streaming.
 *
 * Types here mirror the real @opencode-ai/sdk event shapes:
 * - ToolPart.state is an object with `status` + nested `input`/`output`/`error`
 * - TextPart has `text` but delta comes from `event.properties.delta`
 * - Tool name lives in `ToolPart.tool`, not `ToolPart.name`
 *
 * @module
 */
// ============================================================================
// Event Mapper
// ============================================================================
/**
 * Maps OpenCode SSE events to AgentMessage arrays.
 *
 * Tracks emitted tool IDs to avoid duplicate tool_use/tool_result messages
 * when OpenCode fires multiple `message.part.updated` events for the same tool
 * as it transitions through states.
 *
 * Buffers streaming text deltas and emits a single accumulated `assistant`
 * message when the text part is complete (i.e. a non-text event arrives).
 * This matches the Claude provider behavior where each AgentMessage is a
 * complete response, not a streaming chunk.
 */
export class OpenCodeEventMapper {
    emittedToolUses = new Set();
    emittedToolResults = new Set();
    /** Buffered text content from streaming deltas, keyed by part ID */
    pendingText = null;
    /**
     * Maps an OpenCode SSE event to zero or more AgentMessages.
     *
     * @param event - The raw OpenCode SSE event
     * @param sessionId - Our session ID for filtering
     * @returns Array of AgentMessages (may be empty)
     */
    mapEvent(event, sessionId) {
        // Filter events by session ID
        const eventSessionId = this.extractSessionId(event);
        if (eventSessionId && eventSessionId !== sessionId) {
            return [];
        }
        switch (event.type) {
            case "message.part.updated":
                return this.mapPartUpdated(event);
            case "session.idle": {
                // Flush buffered text before emitting result
                const messages = this.flushPendingText();
                messages.push({
                    type: "result",
                    subtype: "success",
                    raw: event,
                });
                return messages;
            }
            case "session.error": {
                const messages = this.flushPendingText();
                messages.push({
                    type: "error",
                    content: this.extractErrorMessage(event.properties?.error),
                    raw: event,
                });
                return messages;
            }
            // Internal state events - no AgentMessage needed
            case "session.status":
            case "message.updated":
                return [];
            default:
                return [];
        }
    }
    /** Flush any buffered text as a final assistant message. Call after stream ends. */
    flush() {
        return this.flushPendingText();
    }
    /** Reset state for a new conversation turn */
    reset() {
        this.emittedToolUses.clear();
        this.emittedToolResults.clear();
        this.pendingText = null;
    }
    // ----------------------------------------
    // Private
    // ----------------------------------------
    extractSessionId(event) {
        return (event.properties?.sessionID ??
            event.properties?.part?.sessionID ??
            event.properties?.info?.id);
    }
    extractErrorMessage(error) {
        if (!error)
            return "Unknown session error";
        if (typeof error === "string")
            return error;
        if (typeof error === "object" && error !== null) {
            const obj = error;
            if (typeof obj.message === "string")
                return obj.message;
            if (typeof obj.type === "string")
                return obj.type;
        }
        return String(error);
    }
    flushPendingText() {
        if (!this.pendingText || !this.pendingText.content) {
            this.pendingText = null;
            return [];
        }
        const msg = {
            type: "assistant",
            content: this.pendingText.content,
            raw: null,
        };
        this.pendingText = null;
        return [msg];
    }
    mapPartUpdated(event) {
        const part = event.properties?.part;
        if (!part)
            return [];
        switch (part.type) {
            case "text":
                return this.bufferTextPart(part, event);
            case "tool": {
                // Flush buffered text before emitting tool events
                const messages = this.flushPendingText();
                messages.push(...this.mapToolPart(part, event));
                return messages;
            }
            // Non-text parts flush any buffered text
            case "reasoning":
            case "step-start":
            case "step-finish":
            case "agent":
                return this.flushPendingText();
            default:
                return this.flushPendingText();
        }
    }
    bufferTextPart(part, event) {
        const delta = event.properties?.delta;
        const messages = [];
        // If a different text part starts, flush the previous one
        if (this.pendingText && this.pendingText.partId !== part.id) {
            messages.push(...this.flushPendingText());
        }
        if (delta) {
            // Append incremental delta to buffer
            if (this.pendingText) {
                this.pendingText.content += delta;
            }
            else {
                this.pendingText = { partId: part.id, content: delta };
            }
        }
        else if (part.text) {
            // Full text replacement (no delta available)
            this.pendingText = { partId: part.id, content: part.text };
        }
        return messages;
    }
    mapToolPart(part, event) {
        const partId = part.id;
        if (!partId)
            return [];
        const status = part.state?.status;
        const messages = [];
        // Emit tool_use once when running (pending state often has empty input {})
        // Also emit on completed/error if we never saw running state
        if (!this.emittedToolUses.has(partId) && status !== "pending") {
            this.emittedToolUses.add(partId);
            messages.push({
                type: "tool_use",
                tool: {
                    name: part.tool,
                    id: partId,
                    input: part.state?.input,
                },
                raw: event,
            });
        }
        // Emit tool_result once when completed or error
        if ((status === "completed" || status === "error") &&
            !this.emittedToolResults.has(partId)) {
            this.emittedToolResults.add(partId);
            const resultContent = status === "error"
                ? (part.state.error ?? "Tool error")
                : (part.state.output ?? "");
            messages.push({
                type: "tool_result",
                content: resultContent,
                tool: { id: partId },
                raw: event,
            });
        }
        return messages;
    }
}
//# sourceMappingURL=event-mapper.js.map