"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.collapseOrphanToolBetweenAssistantSegments = collapseOrphanToolBetweenAssistantSegments;
exports.useOpenAIResponsesAdapter = useOpenAIResponsesAdapter;
exports.useOpenAIStreamAdapter = useOpenAIStreamAdapter;
const tslib_1 = require("tslib");
const react_1 = require("react");
const consumeOpenAIStream_1 = require("./helpers/consumeOpenAIStream.js");
const contentPartsToMessageContent_1 = require("./helpers/contentPartsToMessageContent.js");
const fetchResponseToStreamEvents_1 = require("./helpers/fetchResponseToStreamEvents.js");
const isFetchResponse_1 = require("./helpers/isFetchResponse.js");
const openAIResponseToMessages_1 = require("./helpers/openAIResponseToMessages.js");
/** Single non-streaming response → TChatMessage[]. For streaming use useOpenAIStreamAdapter. */
function isToolPart(p) {
    return p.type === 'tool';
}
/**
 * When the stream splits the reply into two consecutive assistant messages, the same tool call can
 * appear in both. Moves matching tool parts into the earlier segment and drops the duplicate from
 * the later one so the UI does not show orphaned repeats.
 *
 * @param messages - Chat messages (possibly with duplicate tool parts across adjacent assistants).
 * @returns A shallow-copied message list with orphan duplicates collapsed.
 */
function collapseOrphanToolBetweenAssistantSegments(messages) {
    const result = messages.map((m) => m.role === 'assistant' ? Object.assign({}, m) : m);
    for (let i = result.length - 1; i >= 1; i--) {
        const next = result[i];
        const prev = result[i - 1];
        if (prev.role !== 'assistant' || next.role !== 'assistant') {
            continue;
        }
        const nextContent = next.content;
        if (!Array.isArray(nextContent)) {
            continue;
        }
        const prevContent = prev.content;
        if (!Array.isArray(prevContent)) {
            continue;
        }
        const updatedNextParts = [...nextContent];
        let updatedPrevParts = [...prevContent];
        let changed = false;
        for (let t = updatedNextParts.length - 1; t >= 0; t--) {
            const part = updatedNextParts[t];
            if (!isToolPart(part) || !part.id) {
                continue;
            }
            const pIdx = updatedPrevParts.findIndex((p) => isToolPart(p) && p.id === part.id);
            if (pIdx < 0) {
                continue;
            }
            updatedPrevParts = updatedPrevParts.map((p, j) => (j === pIdx ? part : p));
            updatedNextParts.splice(t, 1);
            changed = true;
        }
        if (!changed) {
            continue;
        }
        result[i - 1] = Object.assign(Object.assign({}, prev), { content: (0, contentPartsToMessageContent_1.contentPartsToMessageContent)(updatedPrevParts) });
        result[i] = Object.assign(Object.assign({}, next), { content: (0, contentPartsToMessageContent_1.contentPartsToMessageContent)(updatedNextParts) });
    }
    return result;
}
/**
 * Index of the assistant message that should receive streamed content for `messageId`.
 * Uses an exact id match when present; otherwise the last assistant (current streaming segment),
 * which covers events that reference an id before our list has caught up.
 *
 * @param messages - Current chat messages while streaming.
 * @param messageId - Target assistant id from the stream event.
 * @returns Index of the assistant to update, or `-1` if there is no assistant.
 */
function resolveStreamContentTargetIndex(messages, messageId) {
    const byId = messages.findIndex((m) => m.id === messageId && m.role === 'assistant');
    if (byId >= 0) {
        return byId;
    }
    for (let i = messages.length - 1; i >= 0; i--) {
        if (messages[i].role === 'assistant') {
            return i;
        }
    }
    return -1;
}
function getResponseIdFromEvent(event) {
    const response = event.response;
    if (!response || typeof response !== 'object') {
        return null;
    }
    const id = response.id;
    return typeof id === 'string' ? id : null;
}
function useOpenAIResponsesAdapter(response) {
    return (0, react_1.useMemo)(() => (0, openAIResponseToMessages_1.openAIResponseToMessages)(response), [response]);
}
function useOpenAIStreamAdapter(stream, options = {}) {
    const { initialMessages = [], assistantMessageId: optionId, onStreamEnd, trackTokenUsage = false, } = options;
    const onStreamEndRef = (0, react_1.useRef)(onStreamEnd);
    onStreamEndRef.current = onStreamEnd;
    const initialMessagesRef = (0, react_1.useRef)(initialMessages);
    initialMessagesRef.current = initialMessages;
    // messageId → output_tokens from response.completed event
    const usageMapRef = (0, react_1.useRef)(new Map());
    const [messages, setMessages] = (0, react_1.useState)(initialMessages);
    const [status, setStatus] = (0, react_1.useState)('ready');
    const [error, setError] = (0, react_1.useState)(null);
    const [responseId, setResponseId] = (0, react_1.useState)(null);
    const assistantMessageId = (0, react_1.useMemo)(() => optionId !== null && optionId !== void 0 ? optionId : `assistant-${Date.now()}`, [optionId]);
    (0, react_1.useEffect)(() => {
        if (!stream) {
            setStatus('ready');
            setError(null);
            setResponseId(null);
            return undefined;
        }
        const sourceStream = (0, isFetchResponse_1.isFetchResponse)(stream)
            ? (0, fetchResponseToStreamEvents_1.fetchResponseToStreamEvents)(stream)
            : stream;
        let cancelled = false;
        setStatus('streaming');
        setError(null);
        setResponseId(null);
        usageMapRef.current = new Map();
        const streamToConsume = {
            [Symbol.asyncIterator]() {
                return tslib_1.__asyncGenerator(this, arguments, function* _a() {
                    var _b, e_1, _c, _d;
                    var _e, _f, _g, _h;
                    try {
                        for (var _j = true, sourceStream_1 = tslib_1.__asyncValues(sourceStream), sourceStream_1_1; sourceStream_1_1 = yield tslib_1.__await(sourceStream_1.next()), _b = sourceStream_1_1.done, !_b; _j = true) {
                            _d = sourceStream_1_1.value;
                            _j = false;
                            const event = _d;
                            const nextResponseId = getResponseIdFromEvent(event);
                            if (!cancelled && nextResponseId) {
                                setResponseId((prev) => (prev === nextResponseId ? prev : nextResponseId));
                            }
                            // Extract token usage from response.completed (only when opt-in)
                            if (trackTokenUsage) {
                                const e = event;
                                const eventType = (_e = e.type) !== null && _e !== void 0 ? _e : e.event;
                                if (eventType === 'response.completed' && e.response) {
                                    const response = e.response;
                                    const tokens = (_g = (_f = response.usage) === null || _f === void 0 ? void 0 : _f.output_tokens) !== null && _g !== void 0 ? _g : 0;
                                    if (tokens > 0) {
                                        ((_h = response.output) !== null && _h !== void 0 ? _h : []).forEach(({ id, type }) => {
                                            if (id && type === 'message') {
                                                usageMapRef.current.set(id, tokens);
                                            }
                                        });
                                    }
                                }
                            }
                            yield yield tslib_1.__await(event);
                        }
                    }
                    catch (e_1_1) { e_1 = { error: e_1_1 }; }
                    finally {
                        try {
                            if (!_j && !_b && (_c = sourceStream_1.return)) yield tslib_1.__await(_c.call(sourceStream_1));
                        }
                        finally { if (e_1) throw e_1.error; }
                    }
                });
            },
        };
        const baseMessages = initialMessagesRef.current;
        const getAssistantMessageId = (index) => index === 0 ? assistantMessageId : `${assistantMessageId}-${index}`;
        setMessages([
            ...baseMessages,
            {
                id: getAssistantMessageId(0),
                role: 'assistant',
                content: '',
            },
        ]);
        const injectUsageMetadata = (msgs) => {
            if (usageMapRef.current.size === 0)
                return msgs;
            return msgs.map((msg) => {
                if (msg.role === 'assistant' && msg.id && usageMapRef.current.has(msg.id)) {
                    return Object.assign(Object.assign({}, msg), { metadata: Object.assign(Object.assign({}, msg.metadata), { outputTokens: usageMapRef.current.get(msg.id) }) });
                }
                return msg;
            });
        };
        const callbacks = {
            baseMessages,
            getAssistantMessageId,
            // Provisional id from getAssistantMessageId → OpenAI item id once the stream provides it.
            onAssistantMessageIdResolved: (previousId, openaiItemId) => {
                if (cancelled)
                    return;
                setMessages((prev) => collapseOrphanToolBetweenAssistantSegments(prev.map((msg) => {
                    if (msg.id === previousId && msg.role === 'assistant') {
                        return Object.assign(Object.assign({}, msg), { id: openaiItemId });
                    }
                    return msg;
                })));
            },
            onContentUpdate: (messageId, content) => {
                if (cancelled)
                    return;
                setMessages((prev) => {
                    const targetIndex = resolveStreamContentTargetIndex(prev, messageId);
                    if (targetIndex < 0) {
                        return collapseOrphanToolBetweenAssistantSegments(prev);
                    }
                    const mapped = prev.map((msg, i) => {
                        if (i !== targetIndex) {
                            return msg;
                        }
                        return Object.assign(Object.assign({}, msg), { content });
                    });
                    return collapseOrphanToolBetweenAssistantSegments(mapped);
                });
            },
            onNewMessage: (messageId) => {
                if (cancelled)
                    return;
                setMessages((prev) => [
                    ...prev,
                    {
                        id: messageId,
                        role: 'assistant',
                        content: '',
                    },
                ]);
            },
            onEnd: (finalMessages, s, err) => {
                var _a;
                setStatus(s === 'done' ? 'ready' : s);
                setError(err !== null && err !== void 0 ? err : null);
                const normalized = injectUsageMetadata(collapseOrphanToolBetweenAssistantSegments(finalMessages));
                if (!cancelled) {
                    setMessages(normalized);
                }
                (_a = onStreamEndRef.current) === null || _a === void 0 ? void 0 : _a.call(onStreamEndRef, normalized);
            },
            getIsCancelled: () => cancelled,
        };
        (0, consumeOpenAIStream_1.consumeOpenAIStream)(streamToConsume, callbacks);
        return () => {
            cancelled = true;
        };
    }, [stream, assistantMessageId]);
    return { messages, status, error, responseId };
}
//# sourceMappingURL=useOpenAIResponsesAdapter.js.map
