import { useCallback, useEffect, useRef } from "react";
import { useEditContextRef, useFieldsEditContextRef, } from "../client/editContext";
import { getComponentById } from "../componentTreeHelper";
import { getBridgeFieldPatchValue, isBridgePatchableTextField, } from "../page-viewer/bridgeFieldPatch";
import { useSlotContext } from "../views/editorSlotContext";
import { normalizeMarkerId } from "../utils";
export function useBridgeInlineEditing({ pageViewContextRef, activeBridgeInlineEditRef, blockIframeBlurUntil, sendBeginInlineEdit, sendCancelInlineEdit, sendApplyFieldPatch, }) {
    const editContextRef = useEditContextRef();
    const fieldsContextRef = useFieldsEditContextRef();
    const slotContext = useSlotContext();
    const slotIdRef = useRef(slotContext?.slotId);
    const lastBridgeInlineValueRef = useRef(new Map());
    const deniedInlineCancelKeysRef = useRef(new Set());
    useEffect(() => {
        slotIdRef.current = slotContext?.slotId;
    }, [slotContext?.slotId]);
    const rejectInlineEdit = useCallback((payload, field, currentField) => {
        if (!payload.elementKey)
            return;
        const denyKey = getInlineEditKey(payload.elementKey, field.fieldId);
        deniedInlineCancelKeysRef.current.add(denyKey);
        activeBridgeInlineEditRef.current = null;
        if (currentField) {
            sendApplyFieldPatch({
                elementKey: payload.elementKey,
                fieldId: field.fieldId,
                isRichText: payload.isRichText,
                source: "reset",
                value: getBridgeFieldPatchValue(currentField),
            });
        }
        sendCancelInlineEdit({
            elementKey: payload.elementKey,
            fieldId: field.fieldId,
            forceReadOnly: true,
        });
    }, [activeBridgeInlineEditRef, sendApplyFieldPatch, sendCancelInlineEdit]);
    const ensureBridgeFieldLock = useCallback(async (field) => {
        const editor = editContextRef.current;
        if (!editor || editor.mode === "suggestions")
            return true;
        if (fieldDescriptorsMatch(editor.lockedField, field))
            return true;
        if (getForeignFieldLockSession(editor, field))
            return false;
        return (await editor.operations.ensureLock(field)) || false;
    }, [editContextRef]);
    const resolveFieldDescriptor = useCallback((fieldId, item, elementKey) => {
        if (!fieldId)
            return undefined;
        const pageView = pageViewContextRef.current;
        const structureField = pageView?.bridgeStructure?.fields.find((field) => (elementKey && field.elementKey === elementKey) ||
            (field.fieldId === fieldId &&
                (!item?.id || field.item?.id === item.id)));
        const fallbackItem = pageView?.page?.item ?? pageView?.pageItemDescriptor;
        const sourceItem = item?.id
            ? item
            : (structureField?.item ?? fallbackItem);
        const id = sourceItem?.id ?? fallbackItem?.id;
        const language = sourceItem?.language ?? fallbackItem?.language;
        const versionValue = sourceItem?.version ?? fallbackItem?.version;
        const version = typeof versionValue === "number" && Number.isFinite(versionValue)
            ? versionValue
            : undefined;
        if (!id || !language || version === undefined)
            return undefined;
        return {
            fieldId,
            item: {
                id,
                language,
                version,
                name: sourceItem?.name ?? fallbackItem?.name,
                displayName: sourceItem?.displayName ?? fallbackItem?.displayName,
                path: sourceItem?.path ?? fallbackItem?.path,
                database: sourceItem?.database ?? fallbackItem?.database,
            },
        };
    }, [pageViewContextRef]);
    const handleFieldValueChanged = useCallback(async (payload) => {
        const editor = editContextRef.current;
        if (!editor || editor.mode === "preview")
            return;
        const field = resolveFieldDescriptor(payload.fieldId, payload.item, payload.elementKey);
        if (!field)
            return;
        const currentField = await editor.itemsRepository.getField(field);
        if (!currentField ||
            !isBridgePatchableTextField(currentField, payload.isRichText)) {
            return;
        }
        const hasLock = await ensureBridgeFieldLock(field);
        if (!hasLock) {
            logBridgeInlineEditDiagnostic("blocked: field locked by another user", {
                fieldId: field.fieldId,
                itemId: field.item.id,
            });
            rejectInlineEdit(payload, field, currentField);
            return;
        }
        // Input can arrive before the delayed click interaction finishes the
        // beginInlineEdit handshake. Mark it active immediately so suggestion
        // preview patches do not rewrite the focused host node and reset caret.
        if (payload.elementKey) {
            const activeEdit = activeBridgeInlineEditRef.current;
            activeBridgeInlineEditRef.current = {
                elementKey: payload.elementKey,
                fieldId: field.fieldId,
                item: field.item,
                modeAtStart: activeEdit &&
                    activeEdit.elementKey === payload.elementKey &&
                    activeEdit.fieldId === field.fieldId
                    ? activeEdit.modeAtStart
                    : editor.mode,
                value: (payload.value ?? "").replaceAll("\u200B", ""),
            };
        }
        const value = (payload.value ?? "").replaceAll("\u200B", "");
        const valueKey = `${field.item.id}:${field.item.language}:${field.item.version}:${field.fieldId}`;
        if (lastBridgeInlineValueRef.current.get(valueKey) === value)
            return;
        lastBridgeInlineValueRef.current.set(valueKey, value);
        const activeEdit = activeBridgeInlineEditRef.current;
        const modeAtStart = activeEdit &&
            activeEdit.elementKey === payload.elementKey &&
            activeEdit.fieldId === field.fieldId
            ? activeEdit.modeAtStart
            : undefined;
        editor.operations.editField({
            field,
            forceMode: modeAtStart === "suggestions" || editor.mode === "suggestions"
                ? "suggestions"
                : undefined,
            originatingSlotId: slotIdRef.current,
            refresh: "none",
            value,
        });
    }, [
        activeBridgeInlineEditRef,
        editContextRef,
        ensureBridgeFieldLock,
        rejectInlineEdit,
        resolveFieldDescriptor,
    ]);
    const endFieldFocus = useCallback(() => {
        editContextRef.current?.operations.onFieldBlur?.();
        fieldsContextRef.current?.setFocusedField(undefined, false);
    }, [editContextRef, fieldsContextRef]);
    const handleInlineEditEnded = useCallback((payload) => {
        const denyKey = getInlineEditKey(payload.elementKey, payload.fieldId);
        if (payload.reason === "cancel" &&
            deniedInlineCancelKeysRef.current.delete(denyKey)) {
            endFieldFocus();
            return;
        }
        handleFieldValueChanged(payload);
        endFieldFocus();
    }, [endFieldFocus, handleFieldValueChanged]);
    const beginInlineEdit = useCallback(async (interaction) => {
        const editor = editContextRef.current;
        if (!editor || !interaction.fieldId || !interaction.elementKey) {
            logBridgeInlineEditDiagnostic("skipped: missing context", {
                hasEditor: !!editor,
                fieldId: interaction.fieldId,
                elementKey: interaction.elementKey,
                kind: interaction.kind,
            });
            return false;
        }
        if (interaction.ctrlKey || interaction.shiftKey || interaction.metaKey) {
            logBridgeInlineEditDiagnostic("skipped: modifier click", {
                fieldId: interaction.fieldId,
                ctrlKey: interaction.ctrlKey,
                shiftKey: interaction.shiftKey,
                metaKey: interaction.metaKey,
            });
            return false;
        }
        if (editor.mode !== "edit" && editor.mode !== "suggestions") {
            logBridgeInlineEditDiagnostic("skipped: mode", {
                mode: editor.mode,
                fieldId: interaction.fieldId,
            });
            return false;
        }
        const page = pageViewContextRef.current?.page;
        if (editor.mode === "edit" && page && !page.item.canWriteItem) {
            logBridgeInlineEditDiagnostic("skipped: item is not writable", {
                fieldId: interaction.fieldId,
                itemId: page.item.id,
            });
            return false;
        }
        const resolvedField = resolveFieldDescriptor(interaction.fieldId, interaction.item, interaction.elementKey);
        if (!resolvedField) {
            logBridgeInlineEditDiagnostic("skipped: unresolved field", {
                fieldId: interaction.fieldId,
                elementKey: interaction.elementKey,
                itemId: interaction.item?.id,
            });
            return false;
        }
        if (editor.showLayoutComponents === false && page) {
            const owner = getComponentById(resolvedField.item.id, page);
            if (owner?.layoutId) {
                logBridgeInlineEditDiagnostic("skipped: layout component hidden", {
                    fieldId: resolvedField.fieldId,
                    itemId: resolvedField.item.id,
                });
                return false;
            }
        }
        const hasKnownOrAcquiredLock = await ensureBridgeFieldLock(resolvedField);
        if (!hasKnownOrAcquiredLock) {
            logBridgeInlineEditDiagnostic("skipped: field locked by another user", {
                fieldId: resolvedField.fieldId,
                itemId: resolvedField.item.id,
            });
            rejectInlineEdit(interaction, resolvedField);
            return false;
        }
        blockIframeBlurUntil(Date.now() + 500);
        const shouldRequestLock = editor.mode !== "suggestions";
        logBridgeInlineEditDiagnostic("requesting lock", {
            fieldId: resolvedField.fieldId,
            itemId: resolvedField.item.id,
            language: resolvedField.item.language,
            version: resolvedField.item.version,
            shouldRequestLock,
        });
        const hasLock = shouldRequestLock &&
            fieldDescriptorsMatch(editor.lockedField, resolvedField)
            ? ((await fieldsContextRef.current?.setFocusedField(resolvedField, false)) ?? false)
            : ((await fieldsContextRef.current?.setFocusedField(resolvedField, shouldRequestLock)) ?? false);
        if (!hasLock) {
            logBridgeInlineEditDiagnostic("skipped: lock failed", {
                fieldId: resolvedField.fieldId,
                itemId: resolvedField.item.id,
            });
            rejectInlineEdit(interaction, resolvedField);
            return false;
        }
        const beginPayload = {
            elementKey: interaction.elementKey,
            fieldId: resolvedField.fieldId,
            clientX: interaction.clientX,
            clientY: interaction.clientY,
        };
        if (typeof interaction.selectionStartOffset === "number" &&
            typeof interaction.selectionEndOffset === "number") {
            beginPayload.selectedText = interaction.selectedText;
            beginPayload.selectionStartOffset = interaction.selectionStartOffset;
            beginPayload.selectionEndOffset = interaction.selectionEndOffset;
        }
        const sent = sendBeginInlineEdit(beginPayload);
        logBridgeInlineEditDiagnostic(sent ? "started" : "command not sent", {
            fieldId: resolvedField.fieldId,
            itemId: resolvedField.item.id,
            elementKey: interaction.elementKey,
        });
        return sent;
    }, [
        blockIframeBlurUntil,
        editContextRef,
        ensureBridgeFieldLock,
        fieldsContextRef,
        pageViewContextRef,
        rejectInlineEdit,
        resolveFieldDescriptor,
        sendBeginInlineEdit,
    ]);
    return {
        beginInlineEdit,
        clearInlineDedupe: () => lastBridgeInlineValueRef.current.clear(),
        endFieldFocus,
        handleFieldValueChanged,
        handleInlineEditEnded,
    };
}
function getInlineEditKey(elementKey, fieldId) {
    return `${elementKey ?? ""}:${normalizeMarkerId(fieldId)}`;
}
function fieldDescriptorsMatch(left, right) {
    if (!left || !right)
        return false;
    return (normalizeMarkerId(left.fieldId) === normalizeMarkerId(right.fieldId) &&
        fieldItemsMatch(left.item, right.item));
}
function fieldItemsMatch(left, right) {
    if (!left || !right)
        return false;
    if (normalizeMarkerId(left.id) !== normalizeMarkerId(right.id))
        return false;
    if (left.language &&
        right.language &&
        left.language.toLowerCase() !== right.language.toLowerCase()) {
        return false;
    }
    if (left.version !== undefined &&
        right.version !== undefined &&
        left.version !== right.version) {
        return false;
    }
    return true;
}
function getForeignFieldLockSession(editor, field) {
    return editor.activeSessions.find((session) => session.sessionId !== editor.sessionId &&
        fieldDescriptorsMatch(session.fieldLock, field));
}
function logBridgeInlineEditDiagnostic(message, details) {
    try {
        const meta = import.meta;
        const params = new URLSearchParams(window.location.search);
        const enabled = meta.env?.DEV ||
            meta.env?.MODE === "development" ||
            window.location.hostname === "localhost" ||
            params.get("parhelia_bridge_debug") === "1" ||
            params.get("parhelia_bridge_debug") === "verbose" ||
            window.localStorage.getItem("parhelia:bridge:debug") === "1" ||
            window.localStorage.getItem("parhelia:bridge:debug") === "true";
        if (!enabled)
            return;
    }
    catch {
        return;
    }
    if (details) {
        console.debug(`[Parhelia bridge] inline edit ${message}`, details);
    }
    else {
        console.debug(`[Parhelia bridge] inline edit ${message}`);
    }
}
//# sourceMappingURL=useBridgeInlineEditing.js.map