import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
import uuid from "react-uuid";
import { executeEditOperation, executeRedo, executeUndo, executeWorkflowCommand, lockField, lockItems, unlockItems, checkOperationImpact, checkItemReferences, } from "../services/editService";
import { DeleteItemsWarning } from "../DeleteItemsWarning";
import React, { useCallback, useMemo, useRef, useState, } from "react";
import { handleErrorResult } from "./helpers";
import { useDebouncedCallback } from "use-debounce";
import { createOrUpdateSuggestedEdit } from "../services/suggestedEditsService";
import { decode } from "html-entities";
import { GUID_REGEX_EXACT, getItemDescriptor } from "../utils";
import { copyItemsNeedsTerminalCompletion, waitForEditOperationTerminal, } from "./waitForEditOperationTerminal";
import { ExternalChangesWarningContent, TreeOperationConfirmContent, UndoDifferentItemContent, } from "./OperationDialogContent";
/** When true, the delete dialog shows "permanently delete (bypass recycle bin)". */
const ENABLE_HARD_DELETE_IN_DELETE_DIALOG = false;
// Track pending suggested edit save requests to prevent race conditions
const pendingSuggestedEditSaves = new Map();
// Track pending suggested edits to prevent creating duplicates before server response
const pendingSuggestedEdits = new Map();
/**
 * Determines if an operation modifies the content tree structure.
 * Tree-modifying operations require confirmation before undo/redo.
 */
function isTreeModifyingOperation(operation) {
    const treeModifyingTypes = [
        "create-item",
        "delete-items",
        "move-items",
        "copy-items",
        "import-items",
    ];
    return treeModifyingTypes.includes(operation.type);
}
// Helper function to clean up pending operations for a deleted suggested edit
export function cleanupPendingSuggestedEditOperations(suggestedEdit, user) {
    const userKey = user?.name || "unknown";
    const pendingFieldKey = `${suggestedEdit.itemId}:${suggestedEdit.mainItemLanguage}:${suggestedEdit.mainItemVersion}:${suggestedEdit.fieldId}:${userKey}`;
    const saveKey = `${suggestedEdit.itemId}:${suggestedEdit.mainItemLanguage}:${suggestedEdit.mainItemVersion}:${suggestedEdit.fieldId}`;
    // Remove from pending operations
    pendingSuggestedEdits.delete(pendingFieldKey);
    pendingSuggestedEditSaves.delete(saveKey);
}
export function getOperationsContext(state, ui) {
    const [editOperationExecuted, setEditOperationExecuted] = useState();
    const [executingEditOperations, setExecutingEditOperations] = useState([]);
    const lastOp = useRef(undefined);
    const undoing = useRef(false);
    // Track operation IDs that were successfully undone/redone but whose React state update
    // hasn't been rendered yet. Cleared on each render (when stateRef.current is up-to-date).
    const recentlyUndoneIds = useRef(new Set());
    const recentlyRedoneIds = useRef(new Set());
    // Cache for field items (template field definitions) - these are static and don't change during editing
    // This prevents refetching on every keystroke since itemsRepository always refetches version 0
    const fieldItemsCache = useRef(new Map());
    const stateRef = useRef(state);
    // Update ref synchronously during render (not via useEffect) to avoid stale state issues
    // This ensures stateRef.current always has the latest state when undo/redo is called
    stateRef.current = state;
    // The render means React has processed pending state updates, so the editHistory in
    // stateRef is now authoritative. Clear the recently-processed tracking sets.
    recentlyUndoneIds.current.clear();
    recentlyRedoneIds.current.clear();
    // Extract values to avoid state object reference issues
    const itemsRepository = stateRef.current.itemsRepository;
    // Debounced version of createOrUpdateSuggestedEdit to prevent excessive API calls
    const debouncedCreateOrUpdateSuggestedEdit = useDebouncedCallback(createOrUpdateSuggestedEdit, 500);
    const executeOp = useCallback(async (op, options = {
        refresh: "immediate",
    }) => {
        console.log("Executing op", op);
        setExecutingEditOperations((ops) => (ops ? [...ops, op] : [op]));
        const result = await executeEditOperation(op, stateRef.current.sessionId);
        // console.log("Result", result);
        handleResult(result, options);
        //await stateRef.current.refreshHistory(op.mainItem);
        //stateRef.current.addToEditHistory(result.data as EditOperation);
        const returnedOp = result.data;
        setEditOperationExecuted(returnedOp);
        // Only remove from executing operations if it's not a long-running operation
        // still in progress. Long-running ops will be removed when WebSocket sends
        // operation:completed or operation:failed
        if (returnedOp.executionStatus !== "executing") {
            setExecutingEditOperations((ops) => ops ? ops.filter((x) => x.id !== op.id) : []);
        }
        return returnedOp;
    }, []);
    const duplicateComponents = useCallback(({ componentIds }) => {
        const op = {
            type: "duplicate-components",
            mainItem: stateRef.current.page?.item.descriptor,
            sourceComponentIds: componentIds,
            date: new Date().toISOString(),
            id: uuid(),
            title: "Duplicate components",
        };
        executeOp(op);
    }, [executeOp]);
    const addComponent = useCallback(async (componentTypeId, placeholderKey, index, pageItem) => {
        const normalizedTypeId = (componentTypeId || "").trim();
        if (!normalizedTypeId ||
            normalizedTypeId.includes("[...]") ||
            !GUID_REGEX_EXACT.test(normalizedTypeId)) {
            return undefined;
        }
        // if (!page) {
        //   console.log("Add component: No page found");
        //   return;
        // }
        const op = {
            type: "add-component",
            mainItem: getItemDescriptor(pageItem),
            // parent: parentId
            //   ? {
            //       id: parentId,
            //       language: page.item.language,
            //       version: page.item.version,
            //     }
            //   : undefined,
            placeholderKey,
            placeholderIndex: index,
            componentTypeId: normalizedTypeId,
            date: new Date().toISOString(),
            id: uuid(),
            title: "Add component",
        };
        const result = await executeOp(op);
        return result;
    }, [executeOp]);
    const handleResult = (result, options) => {
        if (handleErrorResult(result, ui, stateRef.current)) {
            return;
        }
        const executedOps = Array.isArray(result.data)
            ? result.data
            : [result.data];
        for (const executedOp of executedOps) {
            if (executedOp &&
                (executedOp.type === "move-component" ||
                    executedOp.type === "add-component" ||
                    executedOp.type === "link-component" ||
                    executedOp.type === "duplicate-components")) {
                if (executedOp.focus) {
                    stateRef.current.setSelection([executedOp.focus]);
                    stateRef.current.setFocusFieldComponentId(executedOp.focus);
                    // Clear insert mode immediately after selecting the newly added component
                    if (executedOp.type === "add-component") {
                        stateRef.current.setInsertMode(false);
                    }
                }
            }
            ui.onOperationExecuted(executedOp);
        }
        // Trigger a page refresh when requested.
        // We rely on the caller to pass refresh !== "none" (e.g. sidebar field editors),
        // while inline editing intentionally uses refresh:"none" to avoid cursor jumps.
        if (options.refresh && options.refresh !== "none") {
            stateRef.current.requestRefresh(options.refresh === "immediate" ? "immediate" : "waitForQuietPeriod");
        }
    };
    const ensureLock = useCallback(async (field) => {
        console.log("[ensureLock] Called with field:", {
            fieldId: field?.fieldId,
            itemId: field?.item?.id,
            language: field?.item?.language,
            version: field?.item?.version,
            currentLockedField: stateRef.current.lockedField
                ? {
                    fieldId: stateRef.current.lockedField.fieldId,
                    itemId: stateRef.current.lockedField.item.id,
                    language: stateRef.current.lockedField.item.language,
                    version: stateRef.current.lockedField.item.version,
                }
                : null,
            timestamp: new Date().toISOString(),
        });
        if (!field || !field.item) {
            console.log("[ensureLock] Early return: field or field.item is missing");
            return false;
        }
        const isAlreadyLocked = stateRef.current.lockedField?.fieldId === field.fieldId &&
            stateRef.current.lockedField?.item.id === field.item.id &&
            stateRef.current.lockedField?.item.language === field.item.language &&
            stateRef.current.lockedField?.item.version === field.item.version;
        if (isAlreadyLocked) {
            console.log("[ensureLock] Field is already locked, returning true without lockField call");
            return true;
        }
        console.log("[ensureLock] Field is NOT locked, calling lockField service");
        const result = await lockField(field.item, field.fieldId, stateRef.current.sessionId);
        console.log("[ensureLock] lockField service returned:", {
            resultType: result.type,
            success: result.type === "success" ? result.data?.success : undefined,
        });
        if (result.type == "error") {
            console.log("error locking field", result);
        }
        if (handleErrorResult(result, ui, stateRef.current))
            return false;
        if (result.type == "success" && result.data.success) {
            stateRef.current.setLockedField(field);
            console.log("[ensureLock] Successfully locked field, returning true");
            return true;
        }
        console.log("[ensureLock] Failed to lock field, returning false");
        return false;
    }, [ui]);
    const lockItemsAndRefresh = useCallback(async (items) => {
        const item = items[0];
        if (!item)
            return;
        await lockItems(items);
        if (item.id == stateRef.current.item?.id) {
            await stateRef.current.loadItem(item);
        }
        else
            stateRef.current.requestRefresh("immediate");
    }, []);
    const unlockItemsAndRefresh = useCallback(async (items) => {
        const item = items[0];
        if (!item)
            return;
        await unlockItems(items);
        if (item.id == stateRef.current.item?.id) {
            await stateRef.current.loadItem(item);
        }
        else
            stateRef.current.requestRefresh("immediate");
    }, []);
    const executeWorkflowCommandAndRefresh = useCallback(async (item, commandId) => {
        const result = await executeWorkflowCommand(item, commandId, "");
        if (result.type === "success") {
            if (!result.data.succeeded) {
                const errorMessage = {
                    summary: "Workflow command failed",
                    details: result.data.message,
                };
                ui.showErrorToast(errorMessage);
            }
            stateRef.current.requestRefresh("immediate");
        }
    }, [ui]);
    const renameItem = useCallback(async (item, newName) => {
        await executeOp({
            type: "rename-item",
            itemId: item.id,
            mainItem: stateRef.current.page?.item.descriptor || getItemDescriptor(item),
            date: new Date().toISOString(),
            id: uuid(),
            item: getItemDescriptor(item),
            newName,
            title: "Rename item",
        });
        stateRef.current.requestRefresh("immediate");
        // Only properties changed (name/path), not children.
        // Refresh both the operation descriptor and (when applicable) the currently loaded
        // descriptor to handle latest-version descriptor mismatches.
        const currentLoadedItem = stateRef.current.item;
        const descriptorsToRefresh = [item];
        if (currentLoadedItem &&
            currentLoadedItem.id === item.id &&
            currentLoadedItem.language === item.language) {
            descriptorsToRefresh.push(currentLoadedItem.descriptor);
        }
        // Await the refresh to ensure cache is updated and events are fired before returning.
        await itemsRepository.refreshItems(descriptorsToRefresh, {
            properties: true,
            children: false,
        });
        // If the renamed item is currently loaded, force a descriptor reload so
        // UI panels (e.g. Item Info) don't remain one rename behind.
        if (currentLoadedItem &&
            currentLoadedItem.id === item.id &&
            currentLoadedItem.language === item.language) {
            await stateRef.current.loadItem(currentLoadedItem.descriptor);
        }
    }, [itemsRepository, executeOp]);
    const lastEditField = useRef(undefined);
    const editField = useCallback(async ({ field, value, rawValue, refresh = "immediate", }) => {
        if (stateRef.current.mode === "suggestions" && stateRef.current.page) {
            // Create a unique key for this field to track pending saves
            const fieldKey = `${field.item.id}:${field.item.language}:${field.item.version}:${field.fieldId}`;
            // If there's already a save in progress for this field, await it first
            const existingPromise = pendingSuggestedEditSaves.get(fieldKey);
            if (existingPromise) {
                try {
                    await existingPromise;
                }
                catch (error) {
                    // Continue processing even if the previous save failed
                    console.warn("Previous suggested edit save failed:", error);
                }
                // Clean up any stale entries that may not have been properly removed
                if (pendingSuggestedEditSaves.get(fieldKey) === existingPromise) {
                    pendingSuggestedEditSaves.delete(fieldKey);
                }
            }
            const op = await getOrMergeSuggestedEditOp(field, rawValue, value, {
                page: stateRef.current.page,
                user: stateRef.current.user,
                itemsRepository: itemsRepository,
                suggestedEdits: stateRef.current.suggestedEdits,
                setSuggestedEdits: stateRef.current.setSuggestedEdits,
            });
            if (op) {
                // Create and track the save promise
                const savePromise = debouncedCreateOrUpdateSuggestedEdit(op)
                    .then((result) => {
                    // Update the suggested edits state with the result from the server
                    if (result.type === "success" && result.data) {
                        const updatedEdit = result.data;
                        const filtered = stateRef.current.suggestedEdits.filter((edit) => edit.id !== updatedEdit.id);
                        stateRef.current.setSuggestedEdits([...filtered, updatedEdit]);
                    }
                    else if (result.type === "error" ||
                        result.type === "unauthorized") {
                        // Show error toast for failed suggested edit save
                        ui.showErrorToast({
                            summary: result.summary || "Failed to save suggestion",
                            details: result.details,
                        });
                    }
                    return result;
                })
                    .finally(() => {
                    // Remove from pending saves when complete
                    pendingSuggestedEditSaves.delete(fieldKey);
                    // Also remove from pending edits since it's now persisted
                    const pendingFieldKey = `${field.item.id}:${field.item.language}:${field.item.version}:${field.fieldId}:${stateRef.current.user?.name || "unknown"}`;
                    pendingSuggestedEdits.delete(pendingFieldKey);
                });
                pendingSuggestedEditSaves.set(fieldKey, savePromise);
                await savePromise;
            }
            return;
        }
        const saveSequence = await itemsRepository.updateFieldValue(field, stateRef.current.user ?? {
            name: "unknown",
            ai: false,
            agentId: undefined,
        }, true, value, rawValue);
        if (refresh === "immediate") {
            return editFieldImmediate({
                field,
                value,
                rawValue,
                refresh,
                saveSequence,
            });
        }
        const isNewField = lastEditField.current?.fieldId !== field.fieldId ||
            lastEditField.current?.item?.id !== field.item?.id ||
            lastEditField.current?.item?.language !== field.item?.language ||
            lastEditField.current?.item?.version !== field.item?.version;
        if (isNewField) {
            executeEditFieldDebounced.flush();
        }
        lastEditField.current = field;
        // Fix: Pass the saveSequence to avoid calling updateFieldValue twice
        return executeEditFieldDebounced({
            field,
            value,
            rawValue,
            refresh,
            saveSequence,
        });
    }, [itemsRepository, getOrMergeSuggestedEditOp]);
    const editFieldImmediate = useCallback(async ({ field, value, rawValue, refresh = "immediate", saveSequence, }) => {
        const val = rawValue !== undefined ? rawValue : value;
        if (val === undefined)
            return;
        const op = await getEditOP(field, rawValue, value);
        // Fix: Handle getEditOP returning undefined (item/field not found)
        // Clear dirty state so the field doesn't get stuck in "saving" state
        if (!op) {
            console.warn("[editFieldImmediate] Could not create edit operation for field:", field.fieldId);
            itemsRepository.clearDirtyState(field);
            return;
        }
        try {
            const result = await executeOp(op, { refresh });
            // Fix: Check if the operation actually succeeded
            // If result is undefined/null, the operation likely failed
            if (!result) {
                console.warn("[editFieldImmediate] Edit operation returned no result, clearing dirty state");
                itemsRepository.clearDirtyState(field);
                return;
            }
            // Don't refresh here - the websocket handler will trigger a refresh when
            // it receives the item-changed notification from the server. This avoids
            // duplicate refresh calls for the same edit.
            itemsRepository.onFieldSaved(field, val, saveSequence || 0);
        }
        catch (error) {
            // Fix: Handle network errors and other exceptions
            // Clear the dirty state to prevent the field from getting stuck in "saving" state
            console.error("[editFieldImmediate] Error saving field:", error);
            itemsRepository.clearDirtyState(field);
        }
    }, [itemsRepository, executeOp]);
    const executeEditFieldDebounced = useDebouncedCallback(async ({ field, value, rawValue, refresh, saveSequence, }) => {
        // Fix: Use the saveSequence passed from editField instead of calling
        // updateFieldValue again. This prevents the race condition where the
        // field gets marked dirty twice with different sequences.
        return editFieldImmediate({
            field,
            value,
            rawValue,
            refresh,
            saveSequence,
        });
    }, stateRef.current.configuration.debounceFieldEditsInterval * 2, { trailing: true });
    // Note: We intentionally keep a single save debounce window here.
    async function getEditOP(field, rawValue, value) {
        const item = await itemsRepository.getItem(field.item);
        if (!item)
            return;
        // Check the local field items cache first - field items (template field definitions)
        // are static and don't change during an editing session
        let fieldItem = fieldItemsCache.current.get(field.fieldId);
        if (!fieldItem) {
            fieldItem = await itemsRepository.getItem({
                id: field.fieldId,
                language: "en",
                version: 0,
            });
            if (fieldItem) {
                fieldItemsCache.current.set(field.fieldId, fieldItem);
            }
        }
        if (!fieldItem) {
            console.log("Could not fetch field item " + field.fieldId);
            return;
        }
        let op = lastOp.current;
        if (!op ||
            op.undone ||
            op.type !== "edit-field" ||
            !op.itemId ||
            op.itemId !== item.id ||
            op.fieldId !== field.fieldId ||
            op.mainItem.language !== item.language ||
            op.mainItem.version !== item.version) {
            // Use state.page's descriptor only if its language AND version match the field's
            // to prevent race conditions when switching languages or versions quickly
            const page = stateRef.current.page;
            const pageMatchesField = page?.item.language === item.language &&
                page?.item.version === item.version;
            const mainItemDescriptor = pageMatchesField && page ? page.item.descriptor : item.descriptor;
            op = {
                type: "edit-field",
                itemId: item.id,
                fieldId: field.fieldId,
                fieldName: fieldItem.name,
                date: new Date().toISOString(),
                id: uuid(),
                title: "Edit " + item.name,
                focus: item.id + ":" + field.fieldId,
                canUndo: true,
                mainItem: mainItemDescriptor,
                agentId: stateRef.current.user?.agentId,
            };
        }
        op.date = new Date().toISOString();
        op.value = rawValue ?? value;
        // Preserve agentId from user context if available
        if (stateRef.current.user?.agentId) {
            op.agentId = stateRef.current.user.agentId;
        }
        const firstEditInheritedComponent = item.isInheritedFromMasterLanguage &&
            op.mainItem.language != stateRef.current.page?.item.language;
        if (firstEditInheritedComponent)
            op.mainItem.language = stateRef.current.page.item.language;
        lastOp.current = op;
        return op;
    }
    const executeUndoOperation = useCallback(async (ops) => {
        const result = await executeUndo(stateRef.current.sessionId, ops.map((x) => x.id));
        // Update state only AFTER API call completes - no optimistic update
        // This ensures the UI only changes when the operation truly completes
        handleResult(result, { refresh: "immediate" });
        if (result.type === "success") {
            for (const op of ops) {
                recentlyUndoneIds.current.add(op.id);
            }
            // Check if any operations are executing in background (long-running undo)
            const executingOps = result.data.filter((x) => x.executionStatus === "executing");
            if (executingOps.length > 0) {
                // Add to executing operations list - progress will be tracked via WebSocket
                setExecutingEditOperations((prev) => [...prev, ...executingOps]);
                // Return true - the operation is in progress, success will be notified via WebSocket
                return true;
            }
            // All undo operations require a full page refresh to update the UI properly.
            // - undoing add-component: removes the added component from the page
            // - undoing remove-component: restores the deleted component to the page
            // - undoing delete-items: restores deleted items
            // - undoing edit-field: reverts field changes
            // The targeted refresh via itemsRepository.refreshItems is not sufficient because:
            // 1. When undoing add-component, the component no longer exists to refresh
            // 2. When undoing remove-component, the component wasn't in cache
            const hasDeleteOperations = true; // Always refresh after undo
            const itemsToRefresh = result.data
                .map((x) => {
                if (x.type === "edit-field")
                    return {
                        ...x.mainItem,
                        id: x.itemId,
                    };
                if (x.type === "add-component")
                    return {
                        id: x.componentId,
                        language: x.mainItem.language,
                        version: x.mainItem.version,
                    };
                return undefined;
            })
                .flat()
                .filter((x) => x != null);
            await itemsRepository.refreshItems(itemsToRefresh);
            // NOTE: We no longer call requestRefresh here because handleResult (line 716) already
            // triggers an immediate refresh. Calling it again caused duplicate/flooding refreshes
            // which overwhelmed the iframe's requestRefresh and caused race conditions.
            // The handleResult refresh combined with itemsRepository.refreshItems above is sufficient.
            return true;
        }
        else {
            ui.showErrorToast({ summary: result.summary, details: result.details });
            // Refresh history so toolbar state matches server (fixes inconsistent UI under load)
            stateRef.current.refreshHistory?.(stateRef.current.historyMode, stateRef.current.showOnlyMyChanges);
            return false;
        }
    }, [ui, itemsRepository]);
    const undoOperations = useCallback(async (operationIds) => {
        if (!operationIds.length)
            return false;
        if (undoing.current) {
            const deadline = Date.now() + 5000;
            while (undoing.current && Date.now() < deadline) {
                await new Promise((r) => setTimeout(r, 50));
            }
            if (undoing.current)
                return false;
        }
        undoing.current = true;
        try {
            // Map operation IDs to full objects from history, or stubs if not found
            const opsToUndo = operationIds.map((id) => {
                const found = stateRef.current.editHistory.find((o) => o.id === id);
                if (found)
                    return found;
                // Return a stub if not found in local history
                return { id, type: "unknown", undone: false };
            });
            // Sort by date descending (newest first) if we have the dates
            const sortedOps = [...opsToUndo].sort((a, b) => {
                if (!a.date || !b.date)
                    return 0;
                return new Date(b.date).getTime() - new Date(a.date).getTime();
            });
            return await executeUndoOperation(sortedOps);
        }
        finally {
            undoing.current = false;
        }
    }, [executeUndoOperation]);
    const undo = useCallback(async (operationId) => {
        if (undoing.current) {
            const deadline = Date.now() + 5000;
            while (undoing.current && Date.now() < deadline) {
                await new Promise((r) => setTimeout(r, 50));
            }
            if (undoing.current)
                return false;
        }
        undoing.current = true;
        try {
            const currentItem = stateRef.current.item?.descriptor;
            const filterBySession = stateRef.current.showOnlyMyChanges;
            const historyMode = stateRef.current.historyMode;
            let ops;
            // If the caller supplied an operationId that was already undone in this session
            // (React hasn't re-rendered yet, so the UI may hold a stale reference), discard it
            // and fall through to the general selection logic below.
            if (operationId && recentlyUndoneIds.current.has(operationId)) {
                operationId = undefined;
            }
            // If a specific operationId is provided (from the UI), always honor it so the UI and
            // server stay in sync under load. Use the op from history if present (for confirmations),
            // otherwise call the API with just the id (server is source of truth).
            if (operationId) {
                const targetOp = stateRef.current.editHistory.find((x) => x.id === operationId && x.canUndo);
                if (targetOp) {
                    ops = [targetOp];
                }
                else {
                    // Not in local state (e.g. history refreshed under load). Call API with this id
                    // so we don't silently do nothing; server will accept or return an error.
                    ops = [
                        { id: operationId, type: "unknown" },
                    ];
                }
            }
            else {
                // Select undoable operations respecting showOnlyMyChanges and historyMode filters
                ops = stateRef.current.editHistory
                    .filter((x) => {
                    if (!x.canUndo)
                        return false;
                    if (recentlyUndoneIds.current.has(x.id))
                        return false;
                    // Session filter (showOnlyMyChanges)
                    if (filterBySession &&
                        x.sessionId !== stateRef.current.sessionId) {
                        return false;
                    }
                    // Page-centric filter: only include operations for current page and language (any version)
                    if (historyMode === "page-centric" && currentItem) {
                        if (!x.mainItem ||
                            x.mainItem.id !== currentItem.id ||
                            x.mainItem.language !== currentItem.language) {
                            return false;
                        }
                    }
                    // Current-version filter: only include operations for current page, language, and version
                    if (historyMode === "current-version" && currentItem) {
                        if (!x.mainItem ||
                            x.mainItem.id !== currentItem.id ||
                            x.mainItem.language !== currentItem.language ||
                            x.mainItem.version !== currentItem.version) {
                            return false;
                        }
                    }
                    return true;
                })
                    .slice(0, 1);
            }
            if (!ops.length) {
                undoing.current = false;
                return false;
            }
            const firstOp = ops[0];
            if (!firstOp) {
                undoing.current = false;
                return false;
            }
            // When we only have an id (op not in local history), skip confirmations and call API
            if (firstOp.type === "unknown") {
                return await executeUndoOperation(ops);
            }
            // Check if this is a cross-session operation and show warning first
            const isCrossSession = firstOp.sessionId !== stateRef.current.sessionId;
            if (isCrossSession) {
                const confirmed = await new Promise((resolve) => {
                    ui.confirmationDialogRef.current?.confirm({
                        header: "Undo operation from another user / session",
                        message: React.createElement("div", { className: "space-y-3" }, React.createElement("p", null, `This will undo an operation created by ${firstOp.user?.name || "another user"} in another session. Are you sure you want to proceed?`), React.createElement(TreeOperationConfirmContent, {
                            title: firstOp.title,
                            description: firstOp.description,
                        })),
                        acceptLabel: "Undo",
                        rejectLabel: "Cancel",
                        showCancel: false,
                        accept: () => resolve(true),
                        reject: () => resolve(false),
                    });
                });
                if (!confirmed) {
                    undoing.current = false;
                    return false;
                }
            }
            // Check if this is a tree-modifying operation that requires confirmation
            // Skip if already confirmed as cross-session (first confirmation is sufficient)
            if (firstOp && isTreeModifyingOperation(firstOp) && !isCrossSession) {
                return new Promise((resolve) => {
                    const operationTypeName = firstOp.type === "create-item"
                        ? "Create Item"
                        : firstOp.type === "delete-items"
                            ? "Delete Items"
                            : firstOp.type === "move-items"
                                ? "Move Items"
                                : firstOp.type === "copy-items"
                                    ? "Copy Items"
                                    : firstOp.type;
                    ui.confirmationDialogRef.current?.confirm({
                        header: `Undo ${operationTypeName}?`,
                        message: React.createElement(TreeOperationConfirmContent, {
                            title: firstOp.title,
                            description: firstOp.description,
                        }),
                        acceptLabel: "Undo",
                        rejectLabel: "Cancel",
                        showCancel: false,
                        accept: async () => {
                            // After user confirms, proceed with external changes check
                            const impactResult = await checkOperationImpact(stateRef.current.sessionId, firstOp.id, "undo");
                            if (impactResult.type === "success" &&
                                impactResult.data?.hasExternalChanges) {
                                // Show warning dialog about external changes
                                const impact = impactResult.data;
                                ui.confirmationDialogRef.current?.confirm({
                                    header: "Warning: This undo will affect other users' work",
                                    message: React.createElement(ExternalChangesWarningContent, {
                                        impact,
                                    }),
                                    acceptLabel: "Undo anyway",
                                    rejectLabel: "Cancel",
                                    showCancel: false,
                                    accept: async () => {
                                        const result = await executeUndoOperation(ops);
                                        undoing.current = false;
                                        resolve(result);
                                    },
                                    reject: () => {
                                        undoing.current = false;
                                        resolve(false);
                                    },
                                });
                            }
                            else {
                                // No external changes, proceed with undo
                                const result = await executeUndoOperation(ops);
                                undoing.current = false;
                                resolve(result);
                            }
                        },
                        reject: () => {
                            undoing.current = false;
                            resolve(false);
                        },
                    });
                });
            }
            // Check for external changes that would be affected by this undo
            const impactResult = await checkOperationImpact(stateRef.current.sessionId, firstOp.id, "undo");
            if (impactResult.type === "success" &&
                impactResult.data?.hasExternalChanges) {
                // Show warning dialog about external changes
                return new Promise((resolve) => {
                    const impact = impactResult.data;
                    ui.confirmationDialogRef.current?.confirm({
                        header: "Warning: This undo will affect other users' work",
                        message: React.createElement(ExternalChangesWarningContent, {
                            impact,
                        }),
                        acceptLabel: "Undo anyway",
                        rejectLabel: "Cancel",
                        showCancel: false,
                        accept: async () => {
                            const result = await executeUndoOperation(ops);
                            undoing.current = false;
                            resolve(result);
                        },
                        reject: () => {
                            undoing.current = false;
                            resolve(false);
                        },
                    });
                });
            }
            // Check if the operation's mainItem matches the currently loaded item
            if (firstOp &&
                firstOp.mainItem &&
                currentItem &&
                (firstOp.mainItem.id !== currentItem.id ||
                    firstOp.mainItem.language !== currentItem.language ||
                    firstOp.mainItem.version !== currentItem.version)) {
                // Operation is on a different item - ask user if they want to load it first
                return new Promise((resolve) => {
                    ui.confirmationDialogRef.current?.confirm({
                        header: "Undo operation on different item",
                        message: React.createElement(UndoDifferentItemContent, {
                            operationTitle: firstOp.title,
                            itemId: firstOp.mainItem.id,
                            itemLanguage: firstOp.mainItem.language,
                        }),
                        acceptLabel: "Load item first",
                        rejectLabel: "Undo anyway",
                        showCancel: true,
                        accept: async () => {
                            // Load the item first, then user can manually trigger undo
                            await stateRef.current.loadItem(firstOp.mainItem);
                            undoing.current = false;
                            resolve(false); // Return false to indicate undo wasn't executed yet
                        },
                        reject: async () => {
                            // Proceed with undo without loading
                            const result = await executeUndoOperation(ops);
                            undoing.current = false;
                            resolve(result);
                        },
                    });
                });
            }
            // Operation is on current item or no item loaded - proceed normally
            return await executeUndoOperation(ops);
        }
        finally {
            // Always reset the flag
            undoing.current = false;
            console.log("undoing flag reset");
        }
    }, [ui, executeUndoOperation]);
    const executeRedoOperation = useCallback(async (op) => {
        const result = await executeRedo(stateRef.current.sessionId, op.id);
        // NOTE: We no longer update editHistory here. The single source of truth is EditorShell,
        // which merges server responses + websocket updates while enforcing scope filtering.
        handleResult(result, { refresh: "immediate" });
        if (result.type === "success") {
            recentlyRedoneIds.current.add(op.id);
            // Check if the operation is executing in background (long-running redo)
            const executingOps = result.data.filter((x) => x.executionStatus === "executing");
            if (executingOps.length > 0) {
                // Add to executing operations list - progress will be tracked via WebSocket
                setExecutingEditOperations((prev) => [...prev, ...executingOps]);
                // Return true - the operation is in progress, success will be notified via WebSocket
                return true;
            }
            const itemsToRefresh = result.data
                .map((x) => {
                if (x.type === "edit-field")
                    return {
                        ...x.mainItem,
                        id: x.itemId,
                    };
                if (x.type === "add-component")
                    return {
                        id: x.componentId,
                        language: x.mainItem.language,
                        version: x.mainItem.version,
                    };
                return undefined;
            })
                .filter((x) => x != null);
            await itemsRepository.refreshItems(itemsToRefresh);
            return true;
        }
        else {
            ui.showErrorToast({ summary: result.summary, details: result.details });
            stateRef.current.refreshHistory?.(stateRef.current.historyMode, stateRef.current.showOnlyMyChanges);
            return false;
        }
    }, [ui, itemsRepository]);
    const redo = useCallback(async (operationId) => {
        if (undoing.current) {
            const deadline = Date.now() + 5000;
            while (undoing.current && Date.now() < deadline) {
                await new Promise((r) => setTimeout(r, 50));
            }
            if (undoing.current)
                return false;
        }
        undoing.current = true;
        try {
            const currentItem = stateRef.current.item?.descriptor;
            if (!currentItem)
                return false;
            const filterBySession = stateRef.current.showOnlyMyChanges;
            const historyMode = stateRef.current.historyMode;
            let op;
            if (operationId && recentlyRedoneIds.current.has(operationId)) {
                operationId = undefined;
            }
            // If a specific operationId is provided (from the UI), always honor it so the UI and
            // server stay in sync under load. Use the op from history if present (for confirmations),
            // otherwise call the API with just the id (server is source of truth).
            if (operationId) {
                op = stateRef.current.editHistory.find((x) => x.id === operationId && x.canRedo);
                if (!op) {
                    op = {
                        id: operationId,
                        type: "unknown",
                    };
                }
            }
            else {
                // Select redoable operations respecting showOnlyMyChanges and historyMode filters
                op = [...stateRef.current.editHistory].reverse().find((x) => {
                    if (!x.canRedo)
                        return false;
                    if (recentlyRedoneIds.current.has(x.id))
                        return false;
                    // Session filter (showOnlyMyChanges)
                    if (filterBySession && x.sessionId !== stateRef.current.sessionId) {
                        return false;
                    }
                    // Page-centric filter: only include operations for current page and language (any version)
                    if (historyMode === "page-centric" && currentItem) {
                        if (!x.mainItem ||
                            x.mainItem.id !== currentItem.id ||
                            x.mainItem.language !== currentItem.language) {
                            return false;
                        }
                    }
                    // Current-version filter: only include operations for current page, language, and version
                    if (historyMode === "current-version" && currentItem) {
                        if (!x.mainItem ||
                            x.mainItem.id !== currentItem.id ||
                            x.mainItem.language !== currentItem.language ||
                            x.mainItem.version !== currentItem.version) {
                            return false;
                        }
                    }
                    return true;
                });
            }
            if (!op)
                return false;
            // When we only have an id (op not in local history), skip confirmations and call API
            if (op.type === "unknown") {
                return await executeRedoOperation(op);
            }
            // Check if this is a cross-session operation and show warning first
            const isCrossSession = op.sessionId !== stateRef.current.sessionId;
            if (isCrossSession) {
                const confirmed = await new Promise((resolve) => {
                    ui.confirmationDialogRef.current?.confirm({
                        header: "Redo operation from another user",
                        message: `This will redo an operation created by ${op.user?.name || "another user"}. Are you sure you want to proceed?`,
                        acceptLabel: "Redo",
                        rejectLabel: "Cancel",
                        showCancel: false,
                        accept: () => resolve(true),
                        reject: () => resolve(false),
                    });
                });
                if (!confirmed) {
                    undoing.current = false;
                    return false;
                }
            }
            // Check if this is a tree-modifying operation that requires confirmation
            if (isTreeModifyingOperation(op)) {
                return new Promise((resolve) => {
                    const operationTypeName = op.type === "create-item"
                        ? "Create Item"
                        : op.type === "delete-items"
                            ? "Delete Items"
                            : op.type === "move-items"
                                ? "Move Items"
                                : op.type === "copy-items"
                                    ? "Copy Items"
                                    : op.type;
                    ui.confirmationDialogRef.current?.confirm({
                        header: `Redo ${operationTypeName}?`,
                        message: React.createElement(TreeOperationConfirmContent, {
                            title: op.title,
                            description: op.description,
                        }),
                        acceptLabel: "Redo",
                        rejectLabel: "Cancel",
                        showCancel: false,
                        accept: async () => {
                            // After user confirms, proceed with external changes check
                            const impactResult = await checkOperationImpact(stateRef.current.sessionId, op.id, "redo");
                            if (impactResult.type === "success" &&
                                impactResult.data?.hasExternalChanges) {
                                // Show warning dialog about external changes
                                const impact = impactResult.data;
                                ui.confirmationDialogRef.current?.confirm({
                                    header: "Warning: This redo will affect other users' work",
                                    message: React.createElement(ExternalChangesWarningContent, {
                                        impact,
                                    }),
                                    acceptLabel: "Redo anyway",
                                    rejectLabel: "Cancel",
                                    showCancel: false,
                                    accept: async () => {
                                        const success = await executeRedoOperation(op);
                                        undoing.current = false;
                                        resolve(success);
                                    },
                                    reject: () => {
                                        undoing.current = false;
                                        resolve(false);
                                    },
                                });
                            }
                            else {
                                // No external changes, proceed with redo
                                const success = await executeRedoOperation(op);
                                undoing.current = false;
                                resolve(success);
                            }
                        },
                        reject: () => {
                            undoing.current = false;
                            resolve(false);
                        },
                    });
                });
            }
            // Check for external changes that would be affected by this redo
            const impactResult = await checkOperationImpact(stateRef.current.sessionId, op.id, "redo");
            if (impactResult.type === "success" &&
                impactResult.data?.hasExternalChanges) {
                // Show warning dialog about external changes
                return new Promise((resolve) => {
                    const impact = impactResult.data;
                    ui.confirmationDialogRef.current?.confirm({
                        header: "Warning: This redo will affect other users' work",
                        message: React.createElement(ExternalChangesWarningContent, {
                            impact,
                        }),
                        acceptLabel: "Redo anyway",
                        rejectLabel: "Cancel",
                        showCancel: false,
                        accept: async () => {
                            const success = await executeRedoOperation(op);
                            undoing.current = false;
                            resolve(success);
                        },
                        reject: () => {
                            undoing.current = false;
                            resolve(false);
                        },
                    });
                });
            }
            return await executeRedoOperation(op);
        }
        finally {
            // Always reset the flag
            undoing.current = false;
        }
    }, [ui, itemsRepository, executeRedoOperation]);
    const deleteItems = useCallback(async (items, options) => {
        if (items.length === 0)
            return;
        const itemsToDelete = await itemsRepository.getItems(items);
        const itemNames = itemsToDelete.map((x) => x.name).join(", ");
        // Check for references before showing confirmation
        let references = [];
        try {
            const result = await checkItemReferences(items);
            if (result.type === "success" && result.data) {
                references = result.data;
            }
        }
        catch (error) {
            console.error("Failed to check item references:", error);
            // Continue with delete even if reference check fails
        }
        const hasReferences = references.some((ref) => ref.referencingItems.length > 0);
        // Show confirmation dialog (with reference warning if applicable)
        const result = await new Promise((resolve) => {
            const undoCheckboxId = `delete-enable-undo-${Date.now()}`;
            const hardDeleteCheckboxId = `delete-hard-delete-${Date.now()}`;
            const deleteOptions = (_jsxs("div", { className: "mt-4 space-y-3", children: [ENABLE_HARD_DELETE_IN_DELETE_DIALOG ? (_jsx("div", { className: "rounded-md border border-red-200 bg-red-50/60 p-3", children: _jsxs("label", { className: "flex cursor-pointer items-start gap-2", children: [_jsx("input", { type: "checkbox", id: hardDeleteCheckboxId, "data-testid": "delete-hard-delete-checkbox", defaultChecked: options?.hardDelete ?? false, className: "mt-0.5 h-4 w-4 rounded border-gray-300" }), _jsxs("span", { className: "space-y-1", children: [_jsx("span", { className: "block text-sm font-medium text-red-900", children: "Permanently delete (bypass recycle bin)" }), _jsx("span", { className: "block text-xs text-red-800", children: "This skips the recycle bin. Items can only be restored if undo is enabled for this operation." })] })] }) })) : null, _jsxs("label", { className: "flex cursor-pointer items-center gap-2", children: [_jsx("input", { type: "checkbox", id: undoCheckboxId, "data-testid": "delete-enable-undo-checkbox", defaultChecked: options?.canUndo ?? true, className: "h-4 w-4 rounded border-gray-300" }), _jsx("span", { className: "text-sm", children: "Enable undo (serializes complete content)" })] })] }));
            const getEnableUndo = () => {
                const checkbox = document.getElementById(undoCheckboxId);
                return checkbox?.checked ?? options?.canUndo ?? true;
            };
            const getHardDelete = () => {
                if (!ENABLE_HARD_DELETE_IN_DELETE_DIALOG)
                    return false;
                const checkbox = document.getElementById(hardDeleteCheckboxId);
                return checkbox?.checked ?? options?.hardDelete ?? false;
            };
            if (hasReferences) {
                ui.confirmationDialogRef.current?.confirm({
                    header: "Delete Items with References",
                    message: (_jsxs("div", { className: "space-y-2", children: [_jsx("p", { children: "You are about to delete the following items which are referenced by other content:" }), _jsx(DeleteItemsWarning, { references: references }), _jsx("p", { className: "text-muted-foreground mt-4 text-sm", children: "Deleting these items may break links in your content. Are you sure you want to proceed?" }), deleteOptions] })),
                    acceptLabel: "Delete Anyway",
                    rejectLabel: "Cancel",
                    accept: () => resolve({
                        confirmed: true,
                        enableUndo: getEnableUndo(),
                        hardDelete: getHardDelete(),
                    }),
                    reject: () => resolve({
                        confirmed: false,
                        enableUndo: options?.canUndo ?? true,
                        hardDelete: ENABLE_HARD_DELETE_IN_DELETE_DIALOG
                            ? (options?.hardDelete ?? false)
                            : false,
                    }),
                    showCancel: false,
                });
            }
            else {
                // No references - use simple confirmation
                ui.confirmationDialogRef.current?.confirm({
                    header: "Delete Items",
                    message: (_jsxs("div", { children: [_jsxs("p", { children: ["Are you sure you want to delete", " ", _jsx("span", { className: "font-semibold", children: itemNames }), "?"] }), deleteOptions] })),
                    acceptLabel: "Delete",
                    rejectLabel: "Cancel",
                    accept: () => resolve({
                        confirmed: true,
                        enableUndo: getEnableUndo(),
                        hardDelete: getHardDelete(),
                    }),
                    reject: () => resolve({
                        confirmed: false,
                        enableUndo: options?.canUndo ?? true,
                        hardDelete: ENABLE_HARD_DELETE_IN_DELETE_DIALOG
                            ? (options?.hardDelete ?? false)
                            : false,
                    }),
                    showCancel: false,
                });
            }
        });
        if (!result.confirmed)
            return;
        const canUndoValue = result.enableUndo;
        const hardDeleteValue = result.hardDelete;
        const op = {
            type: "delete-items",
            items: items.map(getItemDescriptor),
            mainItem: stateRef.current.page?.item.descriptor ||
                getItemDescriptor(items[0]),
            date: new Date().toISOString(),
            id: uuid(),
            title: `${hardDeleteValue ? "Hard delete" : "Delete"} ${itemNames}`,
            canUndo: canUndoValue,
            hardDelete: hardDeleteValue,
        };
        const returnedOp = await executeOp(op);
        itemsRepository.onItemsDeleted(itemsToDelete.map((x) => ({
            item: x.descriptor,
            parentId: x.parentId,
        })));
        stateRef.current.requestRefresh("immediate");
        return true;
    }, [itemsRepository, executeOp, ui]);
    const moveItems = useCallback(async (items, target, index) => {
        // Use generic edit endpoint for long-running operation support
        const op = {
            type: "move-items",
            itemIds: items.map((i) => i.id),
            targetParentId: target.id,
            targetIndex: index >= 0 ? index : undefined,
            mainItem: getItemDescriptor(target),
            date: new Date().toISOString(),
            id: uuid(),
            title: `Moving ${items.length} item(s)...`,
        };
        await executeOp(op);
    }, [executeOp]);
    const createItem = useCallback(async (parent, templateId, name) => {
        const op = {
            type: "create-item",
            parentId: parent.id,
            templateId: templateId,
            name: name,
            mainItem: stateRef.current.page?.item.descriptor || getItemDescriptor(parent),
            date: new Date().toISOString(),
            id: uuid(),
            title: `Create ${name}`,
            canUndo: true,
        };
        const result = (await executeOp(op));
        if (!result || !result.createdItemId) {
            return;
        }
        itemsRepository.refreshItems([parent]);
        return {
            id: result.createdItemId,
            language: parent.language,
            version: 1,
        };
    }, [itemsRepository, executeOp]);
    const createVersion = useCallback(async (item) => {
        const op = {
            type: "add-version",
            item: getItemDescriptor(item),
            mainItem: stateRef.current.page?.item.descriptor || getItemDescriptor(item),
            date: new Date().toISOString(),
            id: uuid(),
            title: `Add version to ${item.id}`,
            canUndo: true,
        };
        await executeOp(op);
    }, [executeOp]);
    const removeVersion = useCallback(async (item) => {
        const op = {
            type: "remove-version",
            item: getItemDescriptor(item),
            version: item.version,
            mainItem: stateRef.current.page?.item.descriptor || getItemDescriptor(item),
            date: new Date().toISOString(),
            id: uuid(),
            title: `Remove version ${item.version} from ${item.id}`,
            canUndo: true,
        };
        await executeOp(op);
    }, [executeOp]);
    const copyItems = useCallback(async (items, target, index) => {
        // Use generic edit endpoint for long-running operation support
        const op = {
            type: "copy-items",
            sourceItemIds: items.map((i) => i.id),
            targetParentId: target.id,
            targetIndex: index >= 0 ? index : undefined,
            mainItem: getItemDescriptor(target),
            date: new Date().toISOString(),
            id: uuid(),
            title: `Copying ${items.length} item(s)...`,
        };
        let copyResult = (await executeOp(op));
        if (copyItemsNeedsTerminalCompletion(copyResult)) {
            try {
                const terminal = await waitForEditOperationTerminal(copyResult.id, 120_000);
                copyResult = { ...copyResult, ...terminal };
            }
            catch {
                return [];
            }
        }
        // Return created item descriptors from the operation result
        if (copyResult?.createdItemIds) {
            return copyResult.createdItemIds.map((id) => ({
                id,
                language: target.language,
                version: 0,
            }));
        }
        return [];
    }, [executeOp]);
    const duplicateItem = useCallback(async (item, target, name) => {
        // Use generic edit endpoint for long-running operation support
        // Duplicate is a copy to the same parent, positioned after the original
        const op = {
            type: "copy-items",
            sourceItemIds: [item.id],
            targetParentId: target.id,
            targetIndex: undefined, // Will be positioned after source by backend
            mainItem: getItemDescriptor(target),
            date: new Date().toISOString(),
            id: uuid(),
            title: `Duplicating item...`,
            name, // Pass the user-provided name to the backend
        };
        let copyResult = (await executeOp(op));
        if (copyItemsNeedsTerminalCompletion(copyResult)) {
            try {
                const terminal = await waitForEditOperationTerminal(copyResult.id, 120_000);
                copyResult = { ...copyResult, ...terminal };
            }
            catch (e) {
                ui.showErrorToast({
                    summary: "Duplicate failed",
                    details: e?.message || "Operation timed out",
                });
                return;
            }
        }
        if (copyResult.errorMessage) {
            ui.showErrorToast({
                summary: "Duplicate failed",
                details: copyResult.errorMessage,
            });
            return;
        }
        itemsRepository.refreshItems([target]);
        // Return created item descriptor from the operation result
        if (copyResult?.createdItemIds?.length > 0) {
            return {
                id: copyResult.createdItemIds[0],
                language: target.language,
                version: 0,
            };
        }
        return undefined;
    }, [executeOp, itemsRepository, ui]);
    const publishItem = useCallback(async (operation) => {
        const result = (await executeOp(operation, {
            refresh: "immediate",
        }));
        return result;
    }, [executeOp]);
    const onFieldBlur = useCallback(() => {
        // Reset the last operation to force creation of a new operation
        // when the user returns to edit any field after losing focus
        lastOp.current = undefined;
    }, []);
    // Extract state values to avoid object reference changes
    const mode = stateRef.current.mode;
    const page = stateRef.current.page;
    const ops = useMemo(() => ({
        addComponent,
        ensureLock,
        lockItems: lockItemsAndRefresh,
        unlockItems: unlockItemsAndRefresh,
        executeWorkflowCommand: executeWorkflowCommandAndRefresh,
        editField,
        undo,
        undoOperations,
        redo,
        executeEditOperation: executeOp,
        renameItem,
        deleteItems,
        createItem,
        createVersion,
        removeVersion,
        publishItem,
        duplicateComponents,
        undoing: undoing.current,
        moveItems,
        copyItems,
        duplicateItem,
        onFieldBlur,
    }), [
        addComponent,
        ensureLock,
        lockItemsAndRefresh,
        unlockItemsAndRefresh,
        executeWorkflowCommandAndRefresh,
        editField,
        undo,
        undoOperations,
        redo,
        executeOp,
        renameItem,
        deleteItems,
        createItem,
        createVersion,
        removeVersion,
        publishItem,
        duplicateComponents,
        moveItems,
        mode,
        page,
        copyItems,
        duplicateItem,
        onFieldBlur,
    ]);
    // Cleanup function to flush pending debounced operations on unmount
    // This prevents fields from getting stuck in "saving" state when navigating away
    const cleanup = useCallback(() => {
        executeEditFieldDebounced.flush();
    }, []);
    // Function to mark an operation as no longer executing
    // Called when WebSocket receives operation:completed or operation:failed
    const markOperationComplete = useCallback((operationId) => {
        setExecutingEditOperations((ops) => ops ? ops.filter((x) => x.id !== operationId) : []);
    }, []);
    return useMemo(() => ({
        ops,
        context: {
            editOperationExecuted,
            executingEditOperations,
            markOperationComplete,
        },
        cleanup,
    }), [
        executingEditOperations,
        editOperationExecuted,
        markOperationComplete,
        ops,
        cleanup,
    ]);
}
async function getOrMergeSuggestedEditOp(field, rawValue, value, state) {
    const item = await state.itemsRepository.getItem(field.item);
    if (!item)
        return;
    const fieldItem = item.fields.find((f) => f.id === field.fieldId);
    if (!fieldItem) {
        console.log("Could not locate field in item for suggested edit:", field.fieldId);
        return;
    }
    const newVal = rawValue !== undefined ? rawValue : value;
    if (newVal === undefined)
        return;
    if (!state.page) {
        return;
    }
    const page = state.page;
    // Create a unique key for this field to check for pending edits
    const fieldKey = `${field.item.id}:${field.item.language}:${field.item.version}:${field.fieldId}:${state.user?.name || "unknown"}`;
    // First check if there's a pending suggested edit for this field
    const pendingEdit = pendingSuggestedEdits.get(fieldKey);
    if (pendingEdit) {
        // Update the pending edit instead of creating a new one
        pendingEdit.newValue = newVal || "";
        pendingEdit.updated = new Date().toISOString();
        pendingEdit.updatedBy = state.user?.name || "unknown";
        return pendingEdit;
    }
    const matchingSuggestions = state.suggestedEdits.filter((edit) => edit.mainItemId === page.item.descriptor.id &&
        edit.mainItemLanguage === page.item.descriptor.language &&
        edit.itemId === item.id &&
        edit.fieldId === field.fieldId);
    // Attempt to find an existing suggested edit by the same user for this field.
    const existing = matchingSuggestions.find((edit) => edit.mainItemId === page.item.descriptor.id &&
        edit.mainItemLanguage === page.item.descriptor.language &&
        edit.mainItemVersion === page.item.descriptor.version &&
        edit.itemId === item.id &&
        edit.fieldId === field.fieldId &&
        edit.author === (state.user?.name || "unknown") &&
        edit.status === "pending");
    if (existing) {
        // Update the existing suggestion.
        existing.newValue = newVal || "";
        existing.updated = new Date().toISOString();
        existing.updatedBy = state.user?.name || "unknown";
        const nextSuggestedEdits = [...state.suggestedEdits];
        state.suggestedEdits = nextSuggestedEdits;
        // Optionally, you might update the comments or other fields.
        state.setSuggestedEdits(nextSuggestedEdits);
        return existing;
    }
    else {
        // Create a new suggested edit if none exists.
        const newValue = newVal || "";
        const oldValue = fieldItem.rawValue || "";
        if (decode(newValue) === decode(oldValue))
            return;
        const newEdit = {
            id: uuid(),
            mainItemId: state.page?.item.descriptor.id,
            mainItemLanguage: state.page?.item.descriptor.language,
            mainItemVersion: state.page?.item.descriptor.version,
            itemId: item.id,
            fieldId: field.fieldId,
            oldValue: fieldItem.rawValue || "",
            newValue: newVal || "",
            author: state.user?.name || "unknown",
            status: "pending",
            updatedBy: state.user?.name || "unknown",
            created: new Date().toISOString(),
            updated: new Date().toISOString(),
            type: "FieldValue",
        };
        // Add to pending edits to prevent race conditions
        pendingSuggestedEdits.set(fieldKey, newEdit);
        const nextSuggestedEdits = [...state.suggestedEdits, newEdit];
        state.suggestedEdits = nextSuggestedEdits;
        state.setSuggestedEdits(nextSuggestedEdits);
        return newEdit;
    }
}
//# sourceMappingURL=operations.js.map