import { useCallback, useEffect, useRef, useState } from 'react';
import { useLocalStorageState } from '../useLocalStorage';
import { createChatAgent } from '../../lib/chatAgent';
import { attachmentsEqual, sanitizeAttachment } from './attachments';
import { buildThreadVariants, collectAttachmentContext } from './conversation';
import { buildToolApprovalInterruption, toPublicInterruption } from './interruptions';
import { createRunId, restorePendingRun, serializePendingRun } from './pendingRun';
import { createMessageEventUpdater, createReasoningEventUpdater, createToolCallManager } from './responseState';
import { getChatProvider, hasProvider } from '../../lib/providers';
export class ChatAgentCancelledError extends Error {
    constructor(message = 'Chat agent run cancelled', options) {
        super(message);
        this.name = 'ChatAgentCancelledError';
        if (options?.cause !== undefined) {
            // Assign cause manually for broader runtime support
            ;
            this.cause = options.cause;
        }
    }
}
export const isChatAgentCancelledError = (error) => {
    if (!error || typeof error !== 'object')
        return false;
    return error instanceof ChatAgentCancelledError || error.name === 'ChatAgentCancelledError';
};
const formatByteSize = (size) => {
    if (typeof size !== 'number' || !Number.isFinite(size) || size <= 0) {
        return undefined;
    }
    const units = ['B', 'KB', 'MB', 'GB', 'TB'];
    let value = size;
    let unitIndex = 0;
    while (value >= 1024 && unitIndex < units.length - 1) {
        value /= 1024;
        unitIndex += 1;
    }
    const formatted = value >= 10 || unitIndex === 0 ? value.toFixed(0) : value.toFixed(1);
    return `${formatted} ${units[unitIndex]}`;
};
const describeAttachments = (attachments) => {
    if (!attachments || attachments.length === 0) {
        return undefined;
    }
    const lines = attachments.map((attachment) => {
        const name = attachment.name || attachment.id;
        const details = [];
        if (attachment.mimeType) {
            details.push(attachment.mimeType);
        }
        const sizeLabel = formatByteSize(attachment.size);
        if (sizeLabel) {
            details.push(sizeLabel);
        }
        const descriptor = details.length > 0 ? `${name} (${details.join(', ')})` : name;
        const url = attachment.sourceUrl ?? attachment.url;
        return url ? `- ${descriptor}: ${url}` : `- ${descriptor}`;
    });
    return lines.join('\n');
};
const describeSlashCommand = (slashCommand) => {
    if (!slashCommand) {
        return undefined;
    }
    const parts = [`Slash command: /${slashCommand.name}`];
    const argEntries = Object.entries(slashCommand.args ?? {}).filter(([, value]) => typeof value === 'string' && value.trim().length > 0);
    if (argEntries.length > 0) {
        const argsText = argEntries.map(([key, value]) => `- ${key}: ${value}`).join('\n');
        parts.push(`Arguments:\n${argsText}`);
    }
    if (slashCommand.serverName) {
        parts.push(`Server: ${slashCommand.serverName}`);
    }
    return parts.join('\n');
};
const describeToolCalls = (toolCalls) => {
    if (!toolCalls || toolCalls.length === 0) {
        return undefined;
    }
    return toolCalls.map((call) => {
        const status = call.status ? call.status.toLowerCase() : 'unknown';
        const argsSnippet = call.args?.length ? call.args.slice(0, 200) : '';
        const outputSnippet = call.output?.length ? call.output.slice(0, 200) : '';
        const segments = [`- ${call.name} (${status})`];
        if (argsSnippet) {
            segments.push(`  args: ${argsSnippet}`);
        }
        if (outputSnippet) {
            segments.push(`  output: ${outputSnippet}`);
        }
        if (call.error) {
            segments.push(`  error: ${call.error}`);
        }
        return segments.join('\n');
    }).join('\n');
};
const buildProviderMessageContent = (message) => {
    const segments = [];
    const slashCommandSummary = describeSlashCommand(message.slashCommand);
    if (slashCommandSummary) {
        segments.push(slashCommandSummary);
    }
    if (typeof message.content === 'string' && message.content.trim().length > 0) {
        segments.push(message.content.trim());
    }
    const attachmentSummary = describeAttachments(message.attachments);
    if (attachmentSummary) {
        segments.push(`Attachments:\n${attachmentSummary}`);
    }
    const toolSummary = describeToolCalls(message.toolCalls);
    if (toolSummary) {
        segments.push(`Tool calls:\n${toolSummary}`);
    }
    const normalized = segments.join('\n\n').trim();
    return normalized.length > 0 ? normalized : '[No additional content provided]';
};
const buildProviderMessagesFromHistory = (history) => {
    return history.map((message) => ({
        role: message.role,
        content: buildProviderMessageContent(message)
    }));
};
export const useChatAgent = (sessionId, options) => {
    const { value: attachmentCache, update: updateAttachmentCache } = useLocalStorageState(`chat-session-${sessionId}-attachments`, {});
    const { value: chatSession, update: updateChatSession } = useLocalStorageState(`chat-session-${sessionId}`, { chatHistory: [] });
    // State to track the current assistant response as it streams
    const [currentAssistantResponse, setCurrentAssistantResponse] = useState({
        text: '',
        toolCalls: [],
        reasoningEvents: [],
        messageEvents: [],
        interruptions: []
    });
    const [isStreaming, setIsStreaming] = useState(false);
    const [isCancelling, setIsCancelling] = useState(false);
    const [isCancelable, setIsCancelable] = useState(false);
    const toolCallSnapshotRef = useRef([]);
    const abortControllerRef = useRef(null);
    const pendingRunRef = useRef(null);
    const [pendingInterruptions, setPendingInterruptions] = useState([]);
    const previousSessionIdRef = useRef(sessionId);
    const { value: persistedPendingRun, setValue: setPersistedPendingRun, removeValue: removePersistedPendingRun } = useLocalStorageState(`chat-session-${sessionId}-pending-run`, null);
    const setPendingRunInternal = useCallback((next, options) => {
        pendingRunRef.current = next;
        if (!next) {
            setPendingInterruptions([]);
            if (options?.persist !== false) {
                removePersistedPendingRun();
            }
            return;
        }
        const publicInterruptions = next.interruptions.map((entry) => toPublicInterruption(entry));
        setPendingInterruptions(publicInterruptions);
        if (options?.persist === false) {
            return;
        }
        const serialized = serializePendingRun(next);
        if (serialized) {
            setPersistedPendingRun(serialized);
        }
        else {
            removePersistedPendingRun();
        }
    }, [removePersistedPendingRun, setPersistedPendingRun]);
    const clearPendingRun = useCallback(() => {
        setPendingRunInternal(null);
    }, [setPendingRunInternal]);
    useEffect(() => {
        if (previousSessionIdRef.current === sessionId) {
            return;
        }
        previousSessionIdRef.current = sessionId;
        if (abortControllerRef.current && !abortControllerRef.current.signal.aborted) {
            abortControllerRef.current.abort();
        }
        abortControllerRef.current = null;
        toolCallSnapshotRef.current = [];
        setPendingRunInternal(null, { persist: false });
        setCurrentAssistantResponse({ text: '', toolCalls: [], reasoningEvents: [], messageEvents: [], interruptions: [] });
        setIsStreaming(false);
        setIsCancelling(false);
        setIsCancelable(false);
    }, [sessionId, setPendingRunInternal]);
    useEffect(() => {
        let cancelled = false;
        const rehydrate = async () => {
            if (options.providerId === 'anthropic') {
                if (pendingRunRef.current) {
                    setPendingRunInternal(null, { persist: false });
                    setCurrentAssistantResponse((prev) => ({
                        ...prev,
                        interruptions: []
                    }));
                }
                if (persistedPendingRun) {
                    removePersistedPendingRun();
                }
                return;
            }
            if (!persistedPendingRun) {
                if (pendingRunRef.current) {
                    setPendingRunInternal(null, { persist: false });
                    setCurrentAssistantResponse((prev) => ({
                        ...prev,
                        interruptions: []
                    }));
                }
                return;
            }
            if (pendingRunRef.current &&
                pendingRunRef.current.id === persistedPendingRun.id &&
                pendingRunRef.current.updatedAt === persistedPendingRun.updatedAt) {
                setCurrentAssistantResponse((prev) => ({
                    ...prev,
                    text: persistedPendingRun.assistantText,
                    toolCalls: persistedPendingRun.toolCalls.map((entry) => ({ ...entry })),
                    reasoningEvents: persistedPendingRun.reasoningEvents.map((entry) => ({ ...entry })),
                    messageEvents: persistedPendingRun.messageEvents.map((entry) => ({ ...entry })),
                    interruptions: persistedPendingRun.interruptions.map((entry) => ({ ...entry }))
                }));
                return;
            }
            try {
                const providerId = persistedPendingRun.providerId
                    ?? options.providerId
                    ?? 'openai';
                const agent = createChatAgent({
                    ...options,
                    getAttachmentCache: options.getAttachmentCache ?? (() => attachmentCache)
                });
                const restored = await restorePendingRun(persistedPendingRun, agent, providerId);
                if (cancelled) {
                    return;
                }
                setPendingRunInternal(restored, { persist: false });
                setCurrentAssistantResponse({
                    text: restored.assistantText,
                    toolCalls: restored.toolCalls.map((entry) => ({ ...entry })),
                    reasoningEvents: restored.reasoningEvents.map((entry) => ({ ...entry })),
                    messageEvents: restored.messageEvents.map((entry) => ({ ...entry })),
                    interruptions: restored.interruptions.map((entry) => toPublicInterruption(entry))
                });
            }
            catch (error) {
                console.error('Failed to restore pending run state', error);
                if (!cancelled) {
                    removePersistedPendingRun();
                    setPendingRunInternal(null, { persist: false });
                }
            }
        };
        rehydrate();
        return () => {
            cancelled = true;
        };
    }, [
        persistedPendingRun,
        options,
        attachmentCache,
        removePersistedPendingRun,
        setPendingRunInternal,
        setCurrentAssistantResponse
    ]);
    const resetAssistantStream = useCallback(() => {
        setCurrentAssistantResponse({ text: '', toolCalls: [], reasoningEvents: [], messageEvents: [], interruptions: [] });
    }, []);
    const pushChatHistory = useCallback((message) => {
        updateChatSession((prev) => ({
            ...prev,
            chatHistory: [...prev.chatHistory, { ...message, timestamp: Date.now() }]
        }));
    }, [updateChatSession]);
    const getChatHistory = useCallback(() => {
        return chatSession.chatHistory;
    }, [chatSession.chatHistory]);
    const cacheAttachment = useCallback((attachment) => {
        const sanitized = sanitizeAttachment(attachment);
        updateAttachmentCache((prev) => {
            const existing = prev[sanitized.id];
            const merged = {
                ...existing,
                ...sanitized,
                dataUrl: sanitized.dataUrl ?? existing?.dataUrl,
                url: sanitized.url ?? existing?.url,
                sourceUrl: sanitized.sourceUrl ?? existing?.sourceUrl,
                dateAdded: existing?.dateAdded ?? sanitized.dateAdded ?? Date.now()
            };
            if (attachmentsEqual(existing, merged)) {
                return prev;
            }
            return {
                ...prev,
                [merged.id]: merged
            };
        });
    }, [updateAttachmentCache]);
    const removeAttachment = useCallback((attachmentId) => {
        const history = getChatHistory();
        const isReferenced = history.some((message) => (message.attachments ?? []).some((attachment) => attachment.id === attachmentId));
        if (isReferenced) {
            return;
        }
        updateAttachmentCache((prev) => {
            if (!prev[attachmentId]) {
                return prev;
            }
            const next = { ...prev };
            delete next[attachmentId];
            return next;
        });
    }, [getChatHistory, updateAttachmentCache]);
    const invokeChatAgent = useCallback(async (input, handlers = {}) => {
        const payload = typeof input === 'string'
            ? { content: input }
            : input;
        console.log('Invoking chat agent:', payload, sessionId, options);
        resetAssistantStream();
        clearPendingRun();
        setIsStreaming(true);
        setIsCancelling(false);
        if (abortControllerRef.current && !abortControllerRef.current.signal.aborted) {
            abortControllerRef.current.abort();
        }
        const abortController = new AbortController();
        abortControllerRef.current = abortController;
        setIsCancelable(true);
        const sanitizedAttachments = (payload.attachments ?? []).map(sanitizeAttachment);
        const userMessage = {
            role: 'user',
            content: payload.content,
            attachments: sanitizedAttachments,
            slashCommand: payload.slashCommand ? { ...payload.slashCommand } : undefined
        };
        pushChatHistory(userMessage);
        let cacheChanged = false;
        const nextAttachmentCache = { ...attachmentCache };
        for (const attachment of sanitizedAttachments) {
            const existing = nextAttachmentCache[attachment.id];
            const merged = {
                ...existing,
                ...attachment,
                dataUrl: attachment.dataUrl ?? existing?.dataUrl,
                url: attachment.url ?? existing?.url,
                sourceUrl: attachment.sourceUrl ?? existing?.sourceUrl,
                dateAdded: existing?.dateAdded ?? attachment.dateAdded ?? Date.now()
            };
            const hasDifference = !existing ||
                existing.dataUrl !== merged.dataUrl ||
                existing.url !== merged.url ||
                existing.sourceUrl !== merged.sourceUrl ||
                existing.size !== merged.size ||
                existing.mimeType !== merged.mimeType ||
                existing.name !== merged.name ||
                existing.dateAdded !== merged.dateAdded;
            if (hasDifference) {
                cacheChanged = true;
                nextAttachmentCache[attachment.id] = merged;
            }
        }
        if (cacheChanged) {
            updateAttachmentCache(() => nextAttachmentCache);
        }
        const baseHistory = [...chatSession.chatHistory, userMessage];
        const effectiveAttachmentCache = cacheChanged ? nextAttachmentCache : attachmentCache;
        const { modern: modernThread } = buildThreadVariants(baseHistory);
        const attachmentContext = collectAttachmentContext(baseHistory, effectiveAttachmentCache);
        const providerMessages = buildProviderMessagesFromHistory(baseHistory);
        const providerId = options.providerId ?? 'openai';
        if (!hasProvider(providerId)) {
            throw new Error(`Chat provider "${providerId}" is not available in this environment. Ensure the appropriate plugin is registered.`);
        }
        const provider = getChatProvider(providerId);
        const agent = createChatAgent({
            ...options,
            getAttachmentCache: options.getAttachmentCache ?? (() => effectiveAttachmentCache)
        });
        console.debug('[ChatAgent] run starting', {
            sessionId,
            providerId,
            messageCount: providerMessages.length
        });
        let assistantResponse = '';
        let pendingAssistantText = null;
        const upsertReasoningEvent = createReasoningEventUpdater(setCurrentAssistantResponse);
        const upsertMessageEvent = createMessageEventUpdater(setCurrentAssistantResponse);
        const toolCallManager = createToolCallManager({
            setStreamState: setCurrentAssistantResponse,
            snapshotRef: toolCallSnapshotRef
        });
        const providerInput = {
            threadId: sessionId,
            messages: providerMessages,
            signal: abortController.signal,
            abortController,
            context: {
                attachments: attachmentContext
            },
            metadata: {
                thread: modernThread
            }
        };
        const callbacks = {
            onOutputTextDelta: async (delta) => {
                assistantResponse += delta;
                setCurrentAssistantResponse((prev) => ({
                    ...prev,
                    text: assistantResponse
                }));
                if (handlers.onOutputTextDelta) {
                    await handlers.onOutputTextDelta(delta, assistantResponse);
                }
            },
            onMessageOutputCreated: async (finalOutput) => {
                const output = finalOutput || assistantResponse;
                assistantResponse = output;
                pendingAssistantText = output;
                setCurrentAssistantResponse((prev) => ({
                    ...prev,
                    text: output
                }));
                if (handlers.onMessageOutputCreated) {
                    await handlers.onMessageOutputCreated(output);
                }
            },
            onFinalOutputAccessed: async () => {
                clearPendingRun();
            },
            onToolCall: async (name, args, metadata) => {
                const callId = metadata?.callId ?? `${name}-${toolCallManager.getSnapshot().length + 1}`;
                const status = typeof metadata?.status === 'string' && metadata.status.length > 0
                    ? metadata.status
                    : 'in_progress';
                const timestamp = metadata?.timestamp ?? Date.now();
                const normalizedArgs = args ?? '';
                toolCallManager.sync(callId, {
                    name,
                    args: normalizedArgs,
                    status,
                    error: metadata?.error,
                    timestamp,
                    completedAt: status.toLowerCase() === 'completed' ? timestamp : undefined
                });
                if (handlers.onToolCall) {
                    await handlers.onToolCall(name, normalizedArgs, metadata);
                }
            },
            onToolOutput: async (name, output, metadata) => {
                const callId = metadata?.callId ?? `${name}-${toolCallManager.getSnapshot().length + 1}`;
                const timestamp = metadata?.timestamp ?? Date.now();
                const status = typeof metadata?.status === 'string' ? metadata.status : 'completed';
                toolCallManager.sync(callId, {
                    name,
                    status,
                    output,
                    error: metadata?.error,
                    timestamp,
                    completedAt: timestamp
                });
                if (handlers.onToolOutput) {
                    await handlers.onToolOutput(name, output, metadata);
                }
            },
            onReasoningEvent: async (event) => {
                console.log('Reasoning event:', event);
                upsertReasoningEvent(event);
                if (handlers.onReasoningEvent) {
                    await handlers.onReasoningEvent(event);
                }
            },
            onMessageEvent: async (event) => {
                console.log('Message event:', event);
                upsertMessageEvent(event);
                if (handlers.onMessageEvent) {
                    await handlers.onMessageEvent(event);
                }
            }
        };
        const runHandle = await provider.startRun(agent, providerInput, callbacks);
        try {
            const runResult = await runHandle.completed;
            console.debug('[ChatAgent] run completed', {
                hasResult: typeof runResult?.finalOutput === 'string',
                finalOutputLength: (runResult?.finalOutput ?? '').length,
                interruptionCount: Array.isArray(runResult?.interruptions) ? runResult.interruptions.length : 0,
                toolCallCount: toolCallManager.getSnapshot().length
            });
            if (!pendingAssistantText && typeof runResult?.finalOutput === 'string') {
                pendingAssistantText = runResult.finalOutput;
                assistantResponse = runResult.finalOutput;
                setCurrentAssistantResponse((prev) => ({
                    ...prev,
                    text: runResult.finalOutput
                }));
            }
            const providerState = runResult?.state;
            const rawInterruptions = Array.isArray(runResult?.interruptions)
                ? runResult?.interruptions
                : [];
            const finalizedToolCalls = toolCallManager.getSnapshot();
            const finalContent = (pendingAssistantText ?? assistantResponse ?? '').trimEnd();
            if (rawInterruptions.length > 0 && providerState) {
                const runId = createRunId();
                const now = Date.now();
                const internalInterruptions = rawInterruptions.map((item, index) => buildToolApprovalInterruption(runId, item, now + index, index));
                const pendingInterruptionsOnly = internalInterruptions.filter((entry) => entry.status === 'pending');
                if (pendingInterruptionsOnly.length > 0) {
                    const pendingRun = {
                        id: runId,
                        startedAt: now,
                        updatedAt: now,
                        state: providerState,
                        interruptions: internalInterruptions,
                        assistantText: assistantResponse,
                        toolCalls: finalizedToolCalls,
                        reasoningEvents: (currentAssistantResponse.reasoningEvents ?? []).map((event) => ({ ...event })),
                        messageEvents: (currentAssistantResponse.messageEvents ?? []).map((event) => ({ ...event })),
                        providerId
                    };
                    setPendingRunInternal(pendingRun);
                    setCurrentAssistantResponse((prev) => ({
                        ...prev,
                        interruptions: internalInterruptions.map((entry) => toPublicInterruption(entry))
                    }));
                    if (handlers.onInterruptions) {
                        await handlers.onInterruptions(pendingInterruptionsOnly.map((entry) => toPublicInterruption(entry)), { runId });
                    }
                    setIsStreaming(false);
                    return assistantResponse;
                }
            }
            if (finalContent.length > 0 || finalizedToolCalls.length > 0) {
                pushChatHistory({
                    role: 'assistant',
                    content: finalContent,
                    toolCalls: finalizedToolCalls
                });
            }
            toolCallSnapshotRef.current = [];
            resetAssistantStream();
            setPendingRunInternal(null);
            setIsStreaming(false);
            console.debug('[ChatAgent] run resolved successfully; streaming stopped', {
                sessionId,
                finalContentLength: finalContent.length
            });
            return finalContent;
        }
        catch (error) {
            const isAbort = abortController.signal.aborted || (error instanceof Error && error.name === 'AbortError');
            const errorMessage = error instanceof Error ? error.message : String(error);
            if (isAbort) {
                const partialText = (pendingAssistantText ?? assistantResponse ?? '').trimEnd();
                const now = Date.now();
                const inFlightCalls = toolCallManager.getSnapshot();
                for (const call of inFlightCalls) {
                    const status = call.status?.toLowerCase() ?? '';
                    if (status !== 'completed' && status !== 'failed') {
                        toolCallManager.sync(call.id, {
                            name: call.name,
                            status: 'cancelled',
                            completedAt: now
                        });
                    }
                }
                const finalToolCalls = toolCallManager.getSnapshot();
                if (partialText.length > 0 || finalToolCalls.length > 0) {
                    pushChatHistory({
                        role: 'assistant',
                        content: partialText,
                        toolCalls: finalToolCalls
                    });
                }
                toolCallSnapshotRef.current = [];
                resetAssistantStream();
                setCurrentAssistantResponse({
                    text: '',
                    toolCalls: [],
                    reasoningEvents: [],
                    messageEvents: [],
                    interruptions: []
                });
                setPendingRunInternal(null);
                setIsStreaming(false);
                console.debug('[ChatAgent] run cancelled; partial output preserved', {
                    sessionId,
                    textLength: partialText.length,
                    toolCallCount: finalToolCalls.length
                });
                throw new ChatAgentCancelledError('Chat agent run cancelled', { cause: error });
            }
            const failedToolCalls = toolCallManager.getSnapshot();
            const snapshot = failedToolCalls.map((entry) => ({ ...entry }));
            setIsStreaming(false);
            console.debug('[ChatAgent] run failed; streaming stopped', {
                sessionId,
                error: errorMessage
            });
            toolCallSnapshotRef.current = snapshot;
            setCurrentAssistantResponse({
                text: errorMessage,
                toolCalls: snapshot,
                reasoningEvents: [],
                messageEvents: [],
                interruptions: []
            });
            setPendingRunInternal(null);
            throw error;
        }
        finally {
            abortControllerRef.current = null;
            setIsCancelable(false);
            setIsCancelling(false);
            console.debug('[ChatAgent] run cleanup finished', { sessionId });
        }
    }, [
        sessionId,
        options,
        pushChatHistory,
        chatSession.chatHistory,
        resetAssistantStream,
        attachmentCache,
        updateAttachmentCache,
        clearPendingRun,
        setPendingRunInternal,
        currentAssistantResponse
    ]);
    const clearChatHistory = useCallback(() => {
        updateChatSession(() => ({ chatHistory: [] }));
        updateAttachmentCache(() => ({}));
        clearPendingRun();
    }, [clearPendingRun, updateAttachmentCache, updateChatSession]);
    const cancelCurrentRun = useCallback(() => {
        const controller = abortControllerRef.current;
        if (!controller || controller.signal.aborted) {
            return false;
        }
        setIsCancelling(true);
        controller.abort();
        setPendingRunInternal(null);
        return true;
    }, [setPendingRunInternal]);
    const updatePendingInterruptionStatus = useCallback((interruptionId, nextStatus, reason) => {
        const pending = pendingRunRef.current;
        if (!pending) {
            throw new Error('No pending run available to update interruptions');
        }
        const target = pending.interruptions.find((entry) => entry.id === interruptionId);
        if (!target) {
            throw new Error(`Interruption ${interruptionId} not found`);
        }
        if (nextStatus === 'approved') {
            if (!target.source) {
                throw new Error('Unable to approve interruption because the underlying run handle is unavailable. Reload the conversation to refresh state.');
            }
            pending.state.approve(target.source);
        }
        else if (nextStatus === 'rejected') {
            if (!target.source) {
                throw new Error('Unable to reject interruption because the underlying run handle is unavailable. Reload the conversation to refresh state.');
            }
            pending.state.reject(target.source);
        }
        const now = Date.now();
        const updated = {
            ...pending,
            updatedAt: now,
            interruptions: pending.interruptions.map((entry) => {
                if (entry.id !== interruptionId) {
                    return entry;
                }
                return {
                    ...entry,
                    status: nextStatus,
                    message: reason ?? entry.message,
                    updatedAt: now,
                    resolvedAt: nextStatus === 'pending' ? entry.resolvedAt : now
                };
            })
        };
        setPendingRunInternal(updated);
        setCurrentAssistantResponse((prev) => ({
            ...prev,
            interruptions: updated.interruptions.map((entry) => toPublicInterruption(entry))
        }));
        const refreshed = updated.interruptions.find((entry) => entry.id === interruptionId);
        if (!refreshed) {
            throw new Error(`Interruption ${interruptionId} disappeared during update`);
        }
        return toPublicInterruption(refreshed);
    }, [setPendingRunInternal]);
    const approveInterruption = useCallback((interruptionId) => updatePendingInterruptionStatus(interruptionId, 'approved'), [updatePendingInterruptionStatus]);
    const rejectInterruption = useCallback((interruptionId, reason) => updatePendingInterruptionStatus(interruptionId, 'rejected', reason), [updatePendingInterruptionStatus]);
    const resumePendingRun = useCallback(async (handlers = {}) => {
        const pending = pendingRunRef.current;
        if (!pending) {
            throw new Error('No pending run available to resume');
        }
        const unresolved = pending.interruptions.filter((entry) => entry.status === 'pending');
        if (unresolved.length > 0) {
            throw new Error('Resolve pending interruptions before resuming the run');
        }
        console.log('Resuming chat agent run:', pending.id, sessionId, options);
        if (abortControllerRef.current && !abortControllerRef.current.signal.aborted) {
            abortControllerRef.current.abort();
        }
        const abortController = new AbortController();
        abortControllerRef.current = abortController;
        setIsStreaming(true);
        setIsCancelling(false);
        setIsCancelable(true);
        const baseHistory = [...chatSession.chatHistory];
        const { modern: modernThread } = buildThreadVariants(baseHistory);
        const attachmentContext = collectAttachmentContext(baseHistory, attachmentCache);
        const providerMessages = buildProviderMessagesFromHistory(baseHistory);
        const providerId = pending.providerId ?? options.providerId ?? 'openai';
        if (!hasProvider(providerId)) {
            throw new Error(`Chat provider "${providerId}" is not available in this environment. Ensure the appropriate plugin is registered.`);
        }
        const provider = getChatProvider(providerId);
        if (typeof provider.resumeRun !== 'function') {
            throw new Error(`Chat provider "${providerId}" does not support resuming runs`);
        }
        const agent = createChatAgent({
            ...options,
            getAttachmentCache: options.getAttachmentCache ?? (() => attachmentCache)
        });
        const snapshot = {
            ...pending,
            interruptions: pending.interruptions.map((entry) => ({ ...entry }))
        };
        const previousInterruptionViews = snapshot.interruptions.map((entry) => toPublicInterruption(entry));
        setPendingRunInternal(null);
        let assistantResponse = snapshot.assistantText ?? '';
        let pendingAssistantText = null;
        const toolCallManager = createToolCallManager({
            initialLog: snapshot.toolCalls.map((entry) => ({ ...entry })),
            setStreamState: setCurrentAssistantResponse,
            snapshotRef: toolCallSnapshotRef
        });
        setCurrentAssistantResponse({
            text: assistantResponse,
            toolCalls: toolCallManager.getSnapshot(),
            reasoningEvents: snapshot.reasoningEvents.map((event) => ({ ...event })),
            messageEvents: snapshot.messageEvents.map((event) => ({ ...event })),
            interruptions: previousInterruptionViews
        });
        const upsertReasoningEvent = createReasoningEventUpdater(setCurrentAssistantResponse);
        const upsertMessageEvent = createMessageEventUpdater(setCurrentAssistantResponse);
        const resumeInput = {
            threadId: sessionId,
            messages: providerMessages,
            signal: abortController.signal,
            abortController,
            context: {
                attachments: attachmentContext
            },
            metadata: {
                thread: modernThread
            },
            state: snapshot.state
        };
        const callbacks = {
            onOutputTextDelta: async (delta) => {
                assistantResponse += delta;
                setCurrentAssistantResponse((prev) => ({
                    ...prev,
                    text: assistantResponse
                }));
                if (handlers.onOutputTextDelta) {
                    await handlers.onOutputTextDelta(delta, assistantResponse);
                }
            },
            onMessageOutputCreated: async (finalOutput) => {
                const output = finalOutput || assistantResponse;
                assistantResponse = output;
                pendingAssistantText = output;
                setCurrentAssistantResponse((prev) => ({
                    ...prev,
                    text: output
                }));
                if (handlers.onMessageOutputCreated) {
                    await handlers.onMessageOutputCreated(output);
                }
            },
            onFinalOutputAccessed: async () => {
                clearPendingRun();
            },
            onToolCall: async (name, args, metadata) => {
                const callId = metadata?.callId ?? `${name}-${toolCallManager.getSnapshot().length + 1}`;
                const status = typeof metadata?.status === 'string' && metadata.status.length > 0
                    ? metadata.status
                    : 'in_progress';
                const timestamp = metadata?.timestamp ?? Date.now();
                const normalizedArgs = args ?? '';
                toolCallManager.sync(callId, {
                    name,
                    args: normalizedArgs,
                    status,
                    error: metadata?.error,
                    timestamp,
                    completedAt: status.toLowerCase() === 'completed' ? timestamp : undefined
                });
                if (handlers.onToolCall) {
                    await handlers.onToolCall(name, normalizedArgs, metadata);
                }
            },
            onToolOutput: async (name, output, metadata) => {
                const callId = metadata?.callId ?? `${name}-${toolCallManager.getSnapshot().length + 1}`;
                const timestamp = metadata?.timestamp ?? Date.now();
                const status = typeof metadata?.status === 'string' ? metadata.status : 'completed';
                toolCallManager.sync(callId, {
                    name,
                    status,
                    output,
                    error: metadata?.error,
                    timestamp,
                    completedAt: timestamp
                });
                if (handlers.onToolOutput) {
                    await handlers.onToolOutput(name, output, metadata);
                }
            },
            onReasoningEvent: async (event) => {
                console.log('Reasoning event:', event);
                upsertReasoningEvent(event);
                if (handlers.onReasoningEvent) {
                    await handlers.onReasoningEvent(event);
                }
            },
            onMessageEvent: async (event) => {
                console.log('Message event:', event);
                upsertMessageEvent(event);
                if (handlers.onMessageEvent) {
                    await handlers.onMessageEvent(event);
                }
            }
        };
        const runHandle = await provider.resumeRun(agent, resumeInput, callbacks);
        try {
            const runResult = await runHandle.completed;
            if (!pendingAssistantText && typeof runResult?.finalOutput === 'string') {
                pendingAssistantText = runResult.finalOutput;
                assistantResponse = runResult.finalOutput;
                setCurrentAssistantResponse((prev) => ({
                    ...prev,
                    text: runResult.finalOutput
                }));
            }
            const providerState = runResult?.state;
            const rawInterruptions = Array.isArray(runResult?.interruptions)
                ? runResult?.interruptions
                : [];
            const finalizedToolCalls = toolCallManager.getSnapshot();
            const finalContent = (pendingAssistantText ?? assistantResponse ?? '').trimEnd();
            if (rawInterruptions.length > 0 && providerState) {
                const runId = createRunId();
                const now = Date.now();
                const internalInterruptions = rawInterruptions.map((item, index) => buildToolApprovalInterruption(runId, item, now + index, index));
                const pendingInterruptionsOnly = internalInterruptions.filter((entry) => entry.status === 'pending');
                if (pendingInterruptionsOnly.length > 0) {
                    const pendingRun = {
                        id: runId,
                        startedAt: now,
                        updatedAt: now,
                        state: providerState,
                        interruptions: internalInterruptions,
                        assistantText: assistantResponse,
                        toolCalls: toolCallManager.getSnapshot(),
                        reasoningEvents: (currentAssistantResponse.reasoningEvents ?? []).map((event) => ({ ...event })),
                        messageEvents: (currentAssistantResponse.messageEvents ?? []).map((event) => ({ ...event })),
                        providerId
                    };
                    setPendingRunInternal(pendingRun);
                    setCurrentAssistantResponse((prev) => ({
                        ...prev,
                        interruptions: [
                            ...previousInterruptionViews,
                            ...internalInterruptions.map((entry) => toPublicInterruption(entry))
                        ]
                    }));
                    if (handlers.onInterruptions) {
                        await handlers.onInterruptions(pendingInterruptionsOnly.map((entry) => toPublicInterruption(entry)), { runId });
                    }
                    setIsStreaming(false);
                    return assistantResponse;
                }
            }
            if (finalContent.length > 0 || finalizedToolCalls.length > 0) {
                pushChatHistory({
                    role: 'assistant',
                    content: finalContent,
                    toolCalls: finalizedToolCalls
                });
            }
            toolCallSnapshotRef.current = [];
            resetAssistantStream();
            setIsStreaming(false);
            return finalContent;
        }
        catch (error) {
            resetAssistantStream();
            setIsStreaming(false);
            toolCallSnapshotRef.current = [];
            setPendingRunInternal(null);
            if (abortController.signal.aborted || (error instanceof Error && error.name === 'AbortError')) {
                throw new ChatAgentCancelledError('Chat agent run cancelled', { cause: error });
            }
            throw error;
        }
        finally {
            abortControllerRef.current = null;
            setIsCancelable(false);
            setIsCancelling(false);
        }
    }, [
        attachmentCache,
        chatSession.chatHistory,
        createChatAgent,
        options,
        pushChatHistory,
        resetAssistantStream,
        sessionId,
        setPendingRunInternal,
        currentAssistantResponse
    ]);
    return {
        chatHistory: chatSession.chatHistory,
        pushChatHistory,
        getChatHistory,
        invokeChatAgent,
        clearChatHistory,
        currentAssistantResponse,
        isStreaming,
        isCancelling,
        canCancel: isCancelable,
        cancel: cancelCurrentRun,
        cacheAttachment,
        removeAttachment,
        pendingInterruptions,
        hasPendingInterruptions: pendingInterruptions.some((entry) => entry.status === 'pending'),
        pendingRunId: pendingRunRef.current?.id ?? null,
        approveInterruption,
        rejectInterruption,
        resumePendingRun
    };
};
