"use client";
import { jsx as _jsx } from "react/jsx-runtime";
import { useEditContextRef, } from "../client/editContext";
import { useFieldModification } from "../client/fieldModificationStore";
import { useSlotContext } from "../views/editorSlotContext";
import { extractDOMSelectionContext, sanitizeSlateText, } from "../utils/selectionContext";
import { useThrottledCallback, useDebouncedCallback } from "use-debounce";
import { useEffect, useRef, useState, useCallback } from "react";
import { Range } from "slate";
import { ReactSlate } from "./richtext";
import { createSlateRichTextContextMenu } from "./richtext/contextMenuFactory";
// Minimal fallback profile for when profile is null or undefined
const FALLBACK_PROFILE = {
    toolbar: {
        groups: [
            {
                id: "basic-formatting",
                label: "Basic Formatting",
                display: "buttons",
                showIconsOnly: true,
                options: [],
            },
        ],
    },
};
export function RichTextEditorComponent({ field, readOnly, profile, updateFieldValue, showControls, }) {
    const editContextRef = useEditContextRef();
    const slotContext = useSlotContext();
    const [focused, setFocused] = useState(false);
    const [value, setValue] = useState(field.value);
    const editorRef = useRef(null);
    const slateRef = useRef(null);
    const suppressExternalUpdatesRef = useRef(false);
    const suppressTimerRef = useRef(null);
    if (!editContextRef.current)
        return null;
    const fieldItem = field.descriptor.item;
    if (!fieldItem)
        return null;
    // Handle selection change for AI text editing - track selection to editContext
    const handleSelectionChange = useDebouncedCallback(() => {
        if (!focused || !editorRef.current) {
            return;
        }
        const selection = window.getSelection();
        if (!selection || selection.rangeCount === 0) {
            return;
        }
        const range = selection.getRangeAt(0);
        // Find the actual editable content area (not the wrapper that includes toolbar)
        const editableElement = editorRef.current.querySelector(".slate-editable");
        const contentContainer = editableElement || editorRef.current;
        // Check if the selection is within the editable content area
        if (!contentContainer.contains(range.commonAncestorContainer))
            return;
        // Get the raw selected text and sanitize it from Slate's invisible characters
        const rawSelectedText = range.toString();
        const selectedText = sanitizeSlateText(rawSelectedText);
        // Only track if there's actual selected text (not just cursor position)
        if (!selectedText) {
            const isStillFocused = editorRef.current?.contains(document.activeElement);
            // Clear selection range if there's no selected text
            // BUT: only if the focus is still within the editor.
            // If we lost focus to the AI dialog, we want to KEEP the selection range.
            if (isStillFocused &&
                editContextRef.current?.selectedRange?.fieldId === field.id) {
                editContextRef.current?.setSelectedRange(undefined);
            }
            return;
        }
        // Calculate global offsets within the editable content area only
        const getGlobalTextOffset = (container, targetNode, localOffset) => {
            let offset = 0;
            const treeWalker = document.createTreeWalker(container, NodeFilter.SHOW_TEXT, null);
            let currentNode = treeWalker.nextNode();
            while (currentNode) {
                if (currentNode === targetNode) {
                    return offset + localOffset;
                }
                offset += (currentNode.textContent || "").length;
                currentNode = treeWalker.nextNode();
            }
            return offset;
        };
        const globalStartOffset = getGlobalTextOffset(contentContainer, range.startContainer, range.startOffset);
        const globalEndOffset = getGlobalTextOffset(contentContainer, range.endContainer, range.endOffset);
        // Extract plain text context from the editable content area only (excludes toolbar)
        const { contextBefore, contextAfter } = extractDOMSelectionContext(contentContainer, globalStartOffset, globalEndOffset);
        // Capture the current Slate selection for the replaceText callback
        // First try to get the current Slate selection, but if it's null, convert the DOM range to Slate
        let slateSelection = slateRef.current?.getSelection();
        if (!slateSelection && slateRef.current) {
            // If Slate selection is not available, convert the DOM range to Slate selection
            slateSelection = slateRef.current.getSelectionFromDOMRange(range);
        }
        const selectionRange = {
            itemId: fieldItem.id,
            fieldId: field.id,
            language: fieldItem.language,
            version: fieldItem.version,
            startOffset: globalStartOffset,
            endOffset: globalEndOffset,
            text: selectedText,
            contextBefore,
            contextAfter,
            // Create a callback that uses the Slate API to replace text
            replaceText: slateSelection
                ? (newText) => {
                    slateRef.current?.replaceTextAtSelection(slateSelection, newText);
                }
                : undefined,
        };
        // Update selection if it has changed
        const currentRange = editContextRef.current?.selectedRange;
        if (selectionRange.text !== currentRange?.text ||
            (selectionRange.text &&
                selectionRange.fieldId !== currentRange?.fieldId) ||
            selectionRange.itemId !== currentRange?.itemId) {
            editContextRef.current?.setSelectedRange(selectionRange);
        }
    }, 50);
    // Set up selection change listener when focused
    useEffect(() => {
        if (focused) {
            document.addEventListener("selectionchange", handleSelectionChange);
            return () => {
                document.removeEventListener("selectionchange", handleSelectionChange);
            };
        }
    }, [focused, handleSelectionChange]);
    // Field-specific subscription - only rerenders when THIS field changes
    const { modifiedField } = useFieldModification(field.id, fieldItem.id, fieldItem.language, fieldItem.version, slotContext?.fieldModificationStore);
    useEffect(() => {
        // Only update if the editor is not currently being edited by the user
        const isEditorActive = focused && editorRef.current?.contains(document.activeElement);
        // Avoid accepting external value changes briefly after local edits to prevent flicker
        if (suppressExternalUpdatesRef.current)
            return;
        const newValue = field.isHistoric
            ? field.value
            : (modifiedField?.value ?? field.value);
        if (!isEditorActive && newValue !== value) {
            setValue(newValue);
        }
    }, [field.value, field.isHistoric, modifiedField?.value, focused, value]);
    const debouncedSetFieldvalue = useThrottledCallback((value, capturedDescriptor) => {
        if (updateFieldValue) {
            // Use the updateFieldValue prop when provided (WYSIWYG mode)
            // The descriptor is already captured by the parent component
            updateFieldValue(value);
        }
        else {
            // Use direct field editing when updateFieldValue is not provided (Editor mode)
            // Use the captured descriptor from edit time to prevent saving to wrong language
            editContextRef.current?.operations.editField({
                field: capturedDescriptor,
                value: value,
                refresh: "none",
                originatingSlotId: slotContext?.slotId,
            });
        }
    }, editContextRef.current?.configuration.debounceFieldEditsInterval);
    const handleChange = (newValue) => {
        // Treat common HTML representations of "empty" as equivalent so we don't
        // immediately save when the editor normalizes an empty value on focus.
        const isEmptyHtml = (html) => {
            const normalized = (html ?? "").trim();
            return (normalized === "" ||
                normalized === "<p><br></p>" ||
                normalized === "<br>" ||
                normalized === "<p></p>");
        };
        const originalValue = typeof field.value === "string" ? field.value : "";
        // If both the stored value and the new value are semantically empty,
        // skip saving. This prevents an immediate empty save when the field
        // is first focused and Slate normalizes the content.
        if (isEmptyHtml(newValue) && isEmptyHtml(originalValue)) {
            return;
        }
        if (newValue !== value && !readOnly) {
            setValue(newValue);
            // Temporarily suppress external updates to avoid flicker from backend refreshes
            suppressExternalUpdatesRef.current = true;
            if (suppressTimerRef.current)
                clearTimeout(suppressTimerRef.current);
            const debounceMs = editContextRef.current?.configuration.debounceFieldEditsInterval ?? 300;
            const suppressMs = Math.max(1000, debounceMs + 500);
            suppressTimerRef.current = setTimeout(() => {
                suppressExternalUpdatesRef.current = false;
            }, suppressMs);
            // Capture the field descriptor at edit time to prevent saving to wrong language
            debouncedSetFieldvalue(newValue, field.descriptor);
        }
    };
    // Use the profile directly or fall back to the minimal profile
    const editorProfile = profile ?? FALLBACK_PROFILE;
    // Handle keyboard shortcut (Ctrl/Cmd + .) to trigger AI text edit
    const handleKeyDown = useCallback((e) => {
        // Check for Ctrl+. or Cmd+.
        if ((e.ctrlKey || e.metaKey) && e.key === ".") {
            // Allow triggering even without selection - just need to be focused in this editor
            if (focused &&
                editContextRef.current?.focusedField?.fieldId === field.id) {
                e.preventDefault();
                e.stopPropagation();
                // Dispatch custom event to trigger AI dialog
                document.dispatchEvent(new CustomEvent("inline-ai-open"));
                return;
            }
        }
    }, [focused, field.id]);
    const handleContextMenu = useCallback((event) => {
        if (readOnly)
            return;
        if (!editContextRef.current)
            return;
        event.preventDefault();
        event.stopPropagation();
        const editor = slateRef.current?.getEditor();
        if (!editor)
            return;
        const canAddLink = !!editor.selection && !Range.isCollapsed(editor.selection);
        const isLinkActive = editor.isLinkActive();
        const menuItems = createSlateRichTextContextMenu({
            field,
            editContext: editContextRef.current,
            profile: editorProfile,
            onToggleMark: (markId) => {
                editor.toggleMark(markId);
            },
            onAddLink: () => {
                slateRef.current?.openLinkDialog();
            },
            onStripFormatting: () => {
                editor.stripFormatting();
            },
            canAddLink,
            isLinkActive,
            originatingSlotId: slotContext?.slotId,
        });
        if (menuItems.length === 0)
            return;
        editContextRef.current.showContextMenu(event.nativeEvent, menuItems);
    }, [readOnly, editContextRef, field, editorProfile, slotContext?.slotId]);
    // const editorProfile = getDebugProfile(); // Debug profile with all options
    //console.log(editorProfile);
    return (_jsx("div", { ref: editorRef, onKeyDown: handleKeyDown, children: _jsx(ReactSlate, { ref: slateRef, value: value, onChange: handleChange, onFocus: () => setFocused(true), onBlur: () => setFocused(false), onContextMenu: handleContextMenu, readOnly: readOnly, profile: editorProfile, showControls: showControls }) }));
}
//# sourceMappingURL=RichTextEditorComponent.js.map