import { useEffectEvent } from "react";
import { toast } from "sonner";
import { invalidateAiProfilesCache } from "../../services/aiService";
import { cacheEditOperationTerminalResult, EDIT_OPERATION_TERMINAL_EVENT, } from "../waitForEditOperationTerminal";
import { failOperationToast, succeedOperationToast } from "../operationToasts";
import { removeSuggestionAndLinkedComments, upsertSocketComment, } from "./feedbackSocketState";
import { markSuggestedEditRemoved, replacePendingSuggestionForSameField, upsertSuggestedEdit, } from "../../reviews/suggestedEditState";
import { isWorkflowMetadataField } from "../../workflowFields";
export const SOCKET_DIAGNOSTIC_RAW_MESSAGE_MAX_LENGTH = 75_000;
export function safeStringifyForSocketDiagnostics(value) {
    let serialized;
    try {
        serialized = JSON.stringify(value, null, 2);
    }
    catch (error) {
        return `[Unable to serialize WebSocket message for diagnostics: ${error instanceof Error ? error.message : String(error)}]`;
    }
    if (serialized.length <= SOCKET_DIAGNOSTIC_RAW_MESSAGE_MAX_LENGTH) {
        return serialized;
    }
    const omitted = serialized.length - SOCKET_DIAGNOSTIC_RAW_MESSAGE_MAX_LENGTH;
    return `${serialized.slice(0, SOCKET_DIAGNOSTIC_RAW_MESSAGE_MAX_LENGTH)}\n... [truncated ${omitted} characters from WebSocket diagnostic payload]`;
}
function getSocketMessageAgentId(message) {
    const agentId = message?.payload?.agentId;
    return typeof agentId === "string" ? agentId : null;
}
function getSocketMessageSeq(message) {
    const seq = message?.payload?.seq;
    return typeof seq === "number" ? seq : null;
}
function descriptorTargetsPage(descriptor, pageDescriptor) {
    if (!descriptor || !pageDescriptor)
        return false;
    if (descriptor.id !== pageDescriptor.id)
        return false;
    if (descriptor.language &&
        pageDescriptor.language &&
        descriptor.language !== pageDescriptor.language) {
        return false;
    }
    const descriptorVersion = Number(descriptor.version);
    const pageVersion = Number(pageDescriptor.version);
    if (!Number.isFinite(descriptorVersion) || !Number.isFinite(pageVersion)) {
        return true;
    }
    return (descriptorVersion === pageVersion ||
        descriptorVersion === 0 ||
        pageVersion === 0);
}
function getSocketMessageDetail(message) {
    const type = message?.type;
    const payload = message?.payload;
    if (type === "agent:run:delta") {
        const deltaType = payload?.type;
        const functionName = payload?.data?.functionName ||
            payload?.data?.name ||
            payload?.data?.displayName;
        return [deltaType, functionName].filter(Boolean).join(":") || null;
    }
    if (type === "agent:run:status") {
        return payload?.data?.state || payload?.data?.status || null;
    }
    if (type === "agent:run:start") {
        return payload?.agentName || null;
    }
    if (type === "agent:run:complete") {
        return payload?.finalStatus || null;
    }
    if (type === "agent:run:error") {
        return payload?.error || null;
    }
    return null;
}
export function sameItemLanguage(a, b) {
    const aId = typeof a?.id === "string" ? a.id.toLowerCase() : null;
    const bId = typeof b?.id === "string" ? b.id.toLowerCase() : null;
    const aLanguage = typeof a?.language === "string" ? a.language.toLowerCase() : null;
    const bLanguage = typeof b?.language === "string" ? b.language.toLowerCase() : null;
    return (!!aId &&
        !!bId &&
        !!aLanguage &&
        !!bLanguage &&
        aId === bId &&
        aLanguage === bLanguage);
}
export function shouldRefreshNonLatestVersionAfterAddition(messageSessionId, currentSessionId) {
    // The command that created a version in this editor explicitly loads the
    // newly-created descriptor after its HTTP response. Refreshing the old,
    // pinned version from the earlier socket echo races that load and can leave
    // the active page-view context stale. Other sessions still need a refresh.
    return !messageSessionId || messageSessionId !== currentSessionId;
}
export function dispatchSocketMessageListeners(listeners, message) {
    for (const listener of listeners) {
        try {
            const result = listener(message);
            if (result &&
                typeof result === "object" &&
                typeof result.then === "function") {
                void result.catch((error) => {
                    console.error("Socket listener failed asynchronously", error);
                });
            }
        }
        catch (error) {
            console.error("Socket listener failed", error);
        }
    }
}
export function useSocketMessageHandler(deps) {
    const { sessionId, mode, setWebSocketMessages, setActiveSessions, sendClientInfo, itemsRepository, currentItemDescriptor, loadItem, loadItemVersions, setComments, setSuggestedEdits, setActiveFieldActions, setQuotaInfo, requestRefresh, currentItemRef, addRecentEdit, getActiveFieldModificationStore, socketMessageListeners, isItemUsedInCurrentPage, updateOperationInHistory, markOperationComplete, addExecutingOperation, updateExecutingOperationProgress, refreshWorkbox, canvasHasChromeSuppressedTargets, } = deps;
    // Wrap the entire message handler in useEffectEvent to ensure it always
    // accesses the latest values (e.g., viewName in loadItem) without stale closures
    const handleMessage = useEffectEvent(async (event) => {
        if (!event.data?.toString?.().startsWith?.("{"))
            return;
        let message;
        try {
            message = JSON.parse(event.data);
        }
        catch (error) {
            console.error("Failed to parse WebSocket message", error);
            return;
        }
        if (message.type === "user:notification") {
        }
        // Ensure AI profile callers don't get stuck on the TTL cache after updates.
        if (message.type === "agent-profiles-changed") {
            try {
                invalidateAiProfilesCache();
            }
            catch { }
        }
        try {
            const rawMessage = safeStringifyForSocketDiagnostics(message);
            const webSocketMessage = {
                id: Date.now().toString() + Math.random().toString(36).substr(2, 9),
                timestamp: new Date().toISOString(),
                direction: "incoming",
                type: message.type || "unknown",
                payload: message.payload,
                rawMessage,
                agentId: getSocketMessageAgentId(message),
                seq: getSocketMessageSeq(message),
                detail: getSocketMessageDetail(message),
                payloadBytes: rawMessage.length,
            };
            setWebSocketMessages((prev) => [...prev, webSocketMessage].slice(-1000));
        }
        catch { }
        if (message.type === "active-sessions") {
            setActiveSessions((prev) => {
                if (!Array.isArray(message.payload)) {
                    globalThis.__alpacaActiveSessions = prev;
                    return prev;
                }
                const valid = message.payload.filter((s) => s && s.sessionId && s.user);
                if (valid.length === 0 && prev.length > 0) {
                    globalThis.__alpacaActiveSessions = prev;
                    return prev;
                }
                globalThis.__alpacaActiveSessions = valid;
                return valid;
            });
            try {
                const payload = Array.isArray(message.payload)
                    ? message.payload
                    : [];
                const hasMySession = payload.some((s) => s && s.sessionId === sessionId);
                if (!hasMySession)
                    setTimeout(() => sendClientInfo(), 300);
            }
            catch { }
        }
        if (message.type === "session:revoked") {
            try {
                window.dispatchEvent(new CustomEvent("parhelia:session-revoked", {
                    detail: { reason: message.payload?.reason ?? "session-revoked" },
                }));
            }
            catch { }
            return;
        }
        if (message.type === "item-changed") {
            if (message.payload.action === "delete") {
                const parentId = message.payload?.parentId ?? message.payload?.changes?.parentId;
                itemsRepository.onItemsDeleted([
                    { item: message.payload.item, parentId },
                ]);
                if (message.payload.item.id === currentItemDescriptor?.id && parentId) {
                    // Delete events can be stale or replayed (socket reconnect churn),
                    // and navigating away from a live item is destructive to the user's
                    // context. The cache entry was evicted above, so this re-fetch is
                    // authoritative: only leave for the parent when the item is really
                    // gone. Request-level failures keep the navigate behavior so a
                    // genuine deletion (whose fetch may error) still escapes the item.
                    let currentItemGone = true;
                    try {
                        const refreshed = await itemsRepository.getItem(currentItemDescriptor);
                        currentItemGone = !refreshed;
                    }
                    catch {
                        currentItemGone = true;
                    }
                    if (currentItemGone) {
                        loadItem({
                            id: parentId,
                            language: currentItemDescriptor?.language ?? "en",
                            version: 0,
                        });
                    }
                }
            }
            else {
                await itemsRepository.refreshItems([message.payload.item], message.payload.changes);
                const changes = message?.payload?.changes;
                const isVersionChange = changes?.versions === true;
                const versionAction = changes?.versionAction;
                const isOwnChange = !!sessionId &&
                    message.payload?.sessionId === sessionId &&
                    sessionId !== "00000000-0000-0000-0000-000000000000";
                // Invalidate version 0 cache when versions change so "latest" resolves correctly
                if (isVersionChange &&
                    message.payload.item?.id &&
                    message.payload.item?.language) {
                    itemsRepository.invalidateVersionZeroCache(message.payload.item.id, message.payload.item.language);
                }
                if (isVersionChange &&
                    message.payload.item.id === currentItemDescriptor?.id) {
                    await loadItemVersions({ preserveOnError: true });
                }
                // If the current view is "Latest" (v0) and versions changed for the current item,
                // re-resolve latest->concrete version mapping so the iframe can refresh to the new version.
                if (isVersionChange &&
                    currentItemDescriptor?.id === message.payload.item.id &&
                    currentItemDescriptor?.version === 0) {
                    if (!isOwnChange) {
                        toast.info(versionAction === "removed"
                            ? "A version was deleted. Content has been updated."
                            : "Item versions changed. Content has been updated.");
                    }
                    try {
                        window.dispatchEvent(new CustomEvent("parhelia:item-version-added", {
                            detail: {
                                itemId: message.payload?.item?.id ?? null,
                                language: message.payload?.item?.language ?? null,
                                action: versionAction ?? "changed",
                            },
                        }));
                    }
                    catch { }
                }
            }
        }
        if (message.type === "item-version-added") {
            // Invalidate version 0 cache for this item so "latest" resolves to the new version
            const versionAddedItem = message.payload?.item;
            if (versionAddedItem?.id && versionAddedItem?.language) {
                itemsRepository.invalidateVersionZeroCache(versionAddedItem.id, versionAddedItem.language);
            }
            if (currentItemRef.current && currentItemDescriptor) {
                if (sameItemLanguage(versionAddedItem, currentItemDescriptor)) {
                    await loadItemVersions({ preserveOnError: true });
                    // Auto-reload item content if "Latest" (version 0) is selected
                    // IMPORTANT: contentEditorItem.descriptor is the RESOLVED version (e.g. 9),
                    // while the selection can still be "Latest" (version 0).
                    const isLatestSelected = currentItemDescriptor?.version === 0;
                    if (isLatestSelected) {
                        // Only show toast if this version was added by another user/session
                        const isOwnVersion = message.payload.sessionId === sessionId;
                        if (!isOwnVersion) {
                            toast.info("A new version was added. Content has been updated.");
                        }
                        // Reload the item to show the new version's content
                        // Using await to ensure repository is updated before refresh
                        // Keep the selection on "Latest" (v0); the item itself resolves to the newest version.
                        if (currentItemDescriptor) {
                            await loadItem({ ...currentItemDescriptor }, { skipViewChange: true });
                        }
                        // Signal page-view context to re-resolve "Latest" → concrete version mapping.
                        // This avoids relying on itemsRepository.revision (which caused refresh loops while typing).
                        try {
                            window.dispatchEvent(new CustomEvent("parhelia:item-version-added", {
                                detail: {
                                    itemId: message.payload?.item?.id ?? null,
                                    language: message.payload?.item?.language ?? null,
                                },
                            }));
                        }
                        catch { }
                        // We intentionally avoid calling requestRefresh here:
                        // the page-view context will update editUrl to the new concrete version,
                        // and that change will drive an iframe refresh without a race.
                    }
                    else {
                        // Non-latest selections changed elsewhere must refresh. For this
                        // session, the create-version command itself loads the new version;
                        // its socket echo arrives first and must not refresh the old pin.
                        if (shouldRefreshNonLatestVersionAfterAddition(message.payload.sessionId, sessionId)) {
                            requestRefresh("immediate");
                        }
                    }
                }
            }
        }
        if (message.type === "translation-finished") {
            if (currentItemRef.current?.id === message.payload.itemId) {
                // Final refresh when translation finishes to ensure preview is up to date
                // If translation completion produced a new version while we're viewing "Latest" (v0),
                // re-resolve latest->concrete version mapping so the iframe follows the new version.
                const isLatestSelected = currentItemDescriptor?.version === 0;
                if (isLatestSelected) {
                    try {
                        window.dispatchEvent(new CustomEvent("parhelia:item-version-added", {
                            detail: {
                                itemId: message.payload?.itemId ?? null,
                                language: currentItemDescriptor?.language ?? null,
                            },
                        }));
                    }
                    catch { }
                    // Like item-version-added: avoid requestRefresh race; editUrl change will refresh iframe.
                    return;
                }
                requestRefresh("delayed");
            }
        }
        if (message.type === "comment-updated") {
            setComments((x) => upsertSocketComment(x, message.payload.comment));
        }
        if (message.type === "comment-deleted") {
            setComments((x) => x.filter((c) => c.id !== message.payload.commentId));
        }
        if (message.type === "suggested-edit-updated") {
            setSuggestedEdits((x) => {
                const incoming = message.payload.suggestedEdit;
                if (x.some((suggestion) => suggestion.id === incoming.id)) {
                    return upsertSuggestedEdit(x, incoming, { source: "socket" });
                }
                return replacePendingSuggestionForSameField(x, incoming);
            });
        }
        if (message.type === "suggested-edit-deleted") {
            const deletedSuggestionId = message.payload.id;
            // The server is authoritative: tombstone the id so an in-flight local
            // save or a stale refetch cannot re-create the deleted suggestion.
            markSuggestedEditRemoved(deletedSuggestionId);
            setSuggestedEdits((x) => removeSuggestionAndLinkedComments(x, [], deletedSuggestionId)
                .suggestions);
            setComments((x) => removeSuggestionAndLinkedComments([], x, deletedSuggestionId)
                .comments);
        }
        if (message.type === "executing-field-action") {
            const payload = message.payload;
            const fieldId = payload.fieldId;
            const item = payload.item;
            const status = payload.status;
            const msg = payload.message;
            const label = payload.label;
            const normalized = status?.toLowerCase?.() ?? "";
            const isSuccess = normalized === "completed" || normalized === "success";
            const isError = normalized === "failed" || normalized === "error";
            // On success, refresh item(s) and dismiss the field action overlay
            if (isSuccess) {
                try {
                    await itemsRepository.refreshItems([item]);
                }
                catch { }
                requestRefresh("immediate");
                setActiveFieldActions((x) => x.filter((action) => !(action.field.fieldId === fieldId &&
                    action.field.item.id === item.id &&
                    action.field.item.language === item.language &&
                    action.field.item.version === item.version)));
                return;
            }
            setActiveFieldActions((x) => {
                let state;
                state = isError ? "error" : "running";
                const existingActionIndex = x.findIndex((action) => action.field.fieldId === fieldId &&
                    action.field.item.id === item.id &&
                    action.field.item.language === item.language &&
                    action.field.item.version === item.version);
                if (existingActionIndex !== -1) {
                    const newActions = [...x];
                    newActions[existingActionIndex].state = state;
                    newActions[existingActionIndex].message = msg;
                    return newActions;
                }
                else {
                    const fieldDescriptor = { fieldId, item };
                    const newAction = {
                        field: fieldDescriptor,
                        state,
                        message: msg,
                        label,
                    };
                    return [...x, newAction];
                }
            });
        }
        if (message.type === "update-quota")
            setQuotaInfo(message.payload);
        if (message.type === "load-item") {
            const itemDescriptor = message.payload;
            if (itemDescriptor)
                loadItem(itemDescriptor, { skipViewChange: false });
        }
        if (message.type === "edit-operation") {
            const op = message.payload;
            const isOwnOperation = !!op.sessionId && !!sessionId && op.sessionId === sessionId;
            const isOwnNonAiOperation = isOwnOperation &&
                !!op.user &&
                op.user.ai === false &&
                !op.agentId;
            if (op.type === "edit-field") {
                const editFieldOperation = op;
                const isPendingAiFieldOperation = op.executionStatus === "pending" &&
                    !!(editFieldOperation.agentId ||
                        op.agentId ||
                        editFieldOperation.user?.ai);
                const field = await itemsRepository.getField({
                    item: {
                        ...editFieldOperation.mainItem,
                        id: editFieldOperation.itemId,
                    },
                    fieldId: editFieldOperation.fieldId,
                });
                const fieldId = String(editFieldOperation.fieldId ?? "");
                const isIconField = fieldId === "__Icon" ||
                    fieldId.toLowerCase() === "06d5295c-ed2f-4a54-9bf2-26228d113318";
                const pageDescriptor = currentItemRef.current?.descriptor;
                const targetItem = {
                    id: editFieldOperation.itemId,
                    language: editFieldOperation.mainItem?.language,
                    version: editFieldOperation.mainItem?.version,
                };
                const isSamePage = descriptorTargetsPage(op.mainItem, pageDescriptor);
                const shouldRefreshWorkboxForWorkflowField = isWorkflowMetadataField(fieldId, editFieldOperation.fieldName) &&
                    !isOwnNonAiOperation &&
                    op.executionStatus !== "pending" &&
                    (isSamePage || isItemUsedInCurrentPage(targetItem));
                if (shouldRefreshWorkboxForWorkflowField) {
                    refreshWorkbox?.();
                }
                const fieldType = field?.type?.toLowerCase() ?? "";
                if (!field ||
                    isIconField ||
                    (fieldType !== "single-line text" &&
                        fieldType !== "multi-line text" &&
                        fieldType !== "memo" &&
                        fieldType !== "rich text")) {
                    itemsRepository.refreshItems([
                        {
                            ...editFieldOperation.mainItem,
                            id: editFieldOperation.itemId,
                        },
                    ], { properties: true });
                    // Only refresh the page if it is affected. Own non-AI edits are
                    // already visible in the canvas DOM, so their refresh is normally
                    // skipped — except when the page hosts a MultiComponentEditor strip,
                    // whose slide switches re-render from the app's layout data and
                    // would revert an edit the app never refetched.
                    if (isSamePage || isItemUsedInCurrentPage(targetItem)) {
                        if (isOwnNonAiOperation && !canvasHasChromeSuppressedTargets?.()) {
                        }
                        else {
                            requestRefresh(isPendingAiFieldOperation ? "waitForQuietPeriod" : "immediate");
                        }
                    }
                }
                else {
                    // For our own edit-field ops, skip duplicating local field updates but still continue
                    // so the edit history gets refreshed below.
                    if (!isOwnNonAiOperation) {
                        const userForUpdate = editFieldOperation.user ?? {
                            name: "unknown",
                            ai: !!(editFieldOperation.agentId || op.agentId),
                            agentId: editFieldOperation.agentId || op.agentId,
                        };
                        itemsRepository.updateFieldValue({
                            fieldId: editFieldOperation.fieldId,
                            item: {
                                ...editFieldOperation.mainItem,
                                id: editFieldOperation.itemId,
                            },
                        }, userForUpdate, op.executionStatus === "pending", // Set isDirty based on executionStatus
                        editFieldOperation.undone
                            ? editFieldOperation.oldValue
                            : editFieldOperation.value, undefined, getActiveFieldModificationStore?.());
                        if (isSamePage || isItemUsedInCurrentPage(targetItem)) {
                            if (!isPendingAiFieldOperation) {
                                requestRefresh(mode === "preview" ? "delayed" : "immediate");
                            }
                        }
                    }
                    else if (op.executionStatus !== "pending" &&
                        canvasHasChromeSuppressedTargets?.() &&
                        (isSamePage || isItemUsedInCurrentPage(targetItem))) {
                        // Own text edits are already visible in the canvas DOM, so their
                        // refresh is normally skipped — but a MultiComponentEditor strip
                        // re-renders its slides from the app's layout data on every
                        // switch, which would revert an edit the app never refetched.
                        // One refresh per quiet period, not per autosave echo: an
                        // immediate refresh here pipelines full host re-renders into the
                        // very typing that produced the saves (with slow RSC renders this
                        // clobbers the field mid-edit), while the strip only needs the
                        // app data to be fresh by the time the user switches slides.
                        requestRefresh("waitForQuietPeriod");
                    }
                }
            }
            else {
                // For non-field operations, refresh only if the current page matches the operation's main item
                const pageDescriptor = currentItemRef.current?.descriptor;
                if (op.type === "add-version" &&
                    pageDescriptor?.version === 0 &&
                    op.mainItem?.id === pageDescriptor.id &&
                    op.mainItem?.language === pageDescriptor.language) {
                    itemsRepository.invalidateVersionZeroCache(pageDescriptor.id, pageDescriptor.language);
                    await loadItemVersions({ preserveOnError: true });
                    toast.info("A new version was added. Content has been updated.");
                    await loadItem({ ...pageDescriptor }, { skipViewChange: true });
                    return;
                }
                const shouldRefresh = descriptorTargetsPage(op.mainItem, pageDescriptor);
                // Use the same AI-aware check as the edit-field branch:
                // Only skip refresh for own non-AI operations (where the UI
                // already reflects the change locally). AI/agent operations
                // from the same session still need a refresh because the
                // frontend has no local optimistic state for them.
                const isOwnNonAiOperation = isOwnOperation &&
                    !!op.user &&
                    op.user.ai === false &&
                    !op.agentId;
                if (shouldRefresh) {
                    if (isOwnNonAiOperation) {
                    }
                    else {
                        requestRefresh("immediate");
                    }
                }
            }
            // Update history directly with the operation from websocket
            // This avoids race conditions with server fetches and respects the current mode scope
            if (updateOperationInHistory) {
                updateOperationInHistory(op);
            }
        }
        // Long-running operation progress messages
        if (message.type === "operation:started") {
            // A new long-running operation has started
            const payload = message.payload;
            const op = {
                id: payload.operationId,
                type: payload.type,
                title: payload.title,
                // Use sessionId from payload if available, otherwise use current session
                sessionId: payload.sessionId || sessionId,
                date: payload.timestamp || new Date().toISOString(),
                mainItem: payload.mainItem,
                executionStatus: "executing",
                progress: 0,
                statusMessage: payload.statusMessage || "Starting...",
                executionStarted: payload.timestamp,
                // Executing operations cannot be undone/redone
                canUndo: false,
                canRedo: false,
            };
            if (updateOperationInHistory) {
                updateOperationInHistory(op);
            }
            if (payload.operationId) {
                // Feed the executing-operations list (drives the operations indicator)
                addExecutingOperation?.(op);
            }
        }
        if (message.type === "operation:progress") {
            // Update progress on an existing operation
            const payload = message.payload;
            if (updateOperationInHistory) {
                updateOperationInHistory({
                    id: payload.operationId,
                    // Use sessionId from payload if available, otherwise use current session
                    // This ensures the operation matches the scope filter for "my changes"
                    sessionId: payload.sessionId || sessionId,
                    // Include date in case operation doesn't exist in history yet (missed operation:started)
                    date: payload.timestamp || new Date().toISOString(),
                    executionStatus: "executing",
                    progress: payload.progress,
                    statusMessage: payload.statusMessage,
                    // Executing operations cannot be undone/redone
                    canUndo: false,
                    canRedo: false,
                });
            }
            if (payload.operationId) {
                updateExecutingOperationProgress?.(payload.operationId, payload.progress, payload.statusMessage);
            }
        }
        if (message.type === "operation:completed") {
            // Operation completed successfully
            const payload = message.payload;
            const op = {
                id: payload.operationId || payload.id,
                // Use sessionId from payload if available, otherwise use current session
                sessionId: payload.sessionId || sessionId,
                // Include date in case operation doesn't exist in history yet
                date: payload.date || payload.timestamp || new Date().toISOString(),
                type: payload.type,
                title: payload.title,
                description: payload.description,
                executionStatus: "completed",
                progress: 100,
                statusMessage: "Completed",
                executionCompleted: payload.executionCompleted,
                canUndo: payload.canUndo,
                undoSupported: payload.undoSupported,
                undoNotSupportedReason: payload.undoNotSupportedReason,
                mainItem: payload.mainItem,
                createdItemIds: payload.createdItemIds,
                sourceItemIds: payload.sourceItemIds,
                targetParentId: payload.targetParentId,
            };
            if (updateOperationInHistory) {
                updateOperationInHistory(op);
            }
            {
                const oid = payload.operationId || payload.id;
                if (typeof window !== "undefined" && oid) {
                    cacheEditOperationTerminalResult({
                        operationId: oid,
                        id: oid,
                        createdItemIds: payload.createdItemIds,
                        successCount: payload.successCount,
                        failureCount: payload.failureCount,
                        results: payload.results,
                        summary: payload.summary,
                    });
                    window.dispatchEvent(new CustomEvent(EDIT_OPERATION_TERMINAL_EVENT, {
                        detail: {
                            operationId: oid,
                            id: oid,
                            createdItemIds: payload.createdItemIds,
                            successCount: payload.successCount,
                            failureCount: payload.failureCount,
                            results: payload.results,
                            summary: payload.summary,
                        },
                    }));
                }
            }
            {
                // The completed payload is the full serialized operation, which has
                // `id` rather than `operationId`
                const oid = payload.operationId || payload.id;
                // Remove from executing operations list
                if (markOperationComplete && oid) {
                    markOperationComplete(oid);
                }
                if (oid) {
                    succeedOperationToast(oid, payload.title, payload.summary, payload.type);
                }
                if (payload.type === "publish-item") {
                    // Publish state in the workbox only changes once the job finishes
                    refreshWorkbox?.();
                }
            }
            // Refresh if it affects current page
            const pageDescriptor = currentItemRef.current?.descriptor;
            if (pageDescriptor &&
                payload.mainItem &&
                payload.mainItem.id === pageDescriptor.id) {
                requestRefresh("immediate");
            }
        }
        if (message.type === "operation:failed") {
            // Operation failed
            const payload = message.payload;
            const op = {
                id: payload.operationId || payload.id,
                // Use sessionId from payload if available, otherwise use current session
                sessionId: payload.sessionId || sessionId,
                // Include date in case operation doesn't exist in history yet
                date: payload.date || payload.timestamp || new Date().toISOString(),
                type: payload.type,
                title: payload.title,
                executionStatus: "failed",
                errorMessage: payload.errorMessage,
                statusMessage: "Failed",
                executionCompleted: payload.executionCompleted,
                canUndo: payload.canUndo,
            };
            if (updateOperationInHistory) {
                updateOperationInHistory(op);
            }
            {
                const oid = payload.operationId || payload.id;
                if (typeof window !== "undefined" && oid) {
                    cacheEditOperationTerminalResult({
                        operationId: oid,
                        id: oid,
                        errorMessage: payload.errorMessage ?? "Operation failed",
                        summary: payload.summary,
                    });
                    window.dispatchEvent(new CustomEvent(EDIT_OPERATION_TERMINAL_EVENT, {
                        detail: {
                            operationId: oid,
                            id: oid,
                            errorMessage: payload.errorMessage ?? "Operation failed",
                        },
                    }));
                }
            }
            {
                const oid = payload.operationId || payload.id;
                // Remove from executing operations list
                if (markOperationComplete && oid) {
                    markOperationComplete(oid);
                }
                if (oid) {
                    failOperationToast(oid, payload.errorMessage, payload.title, payload.type);
                }
                if (payload.type === "publish-item") {
                    // A multi-target/language publish can partially succeed before failing
                    refreshWorkbox?.();
                }
            }
        }
        // Handle agent dialog requests
        if (message.type === "agent-dialog-request") {
            const request = message.payload;
            if (request && request.callbackId) {
                // Dispatch custom event for AgentDialogHandler to pick up
                window.dispatchEvent(new CustomEvent("agent:dialog:request", { detail: request }));
            }
        }
        dispatchSocketMessageListeners(socketMessageListeners.current, message);
    });
    return handleMessage;
}
//# sourceMappingURL=useSocketMessageHandler.js.map