import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
import React, { forwardRef, memo, useCallback, useEffect, useImperativeHandle, useMemo, useRef, useState, } from "react";
import { Link2Off } from "lucide-react";
import { createEditor, Editor, Element, Transforms, Range, } from "slate";
import { withHistory } from "slate-history";
import { Editable, ReactEditor, Slate, useSlateSelector, withReact, } from "slate-react";
import "./ReactSlate.css";
import { SLATE_ALIGNMENTS, SLATE_BLOCKS, SLATE_MARKS, } from "../types";
import { LinkEditorDialog } from "../../../LinkEditorDialog";
import EditorDropdown from "./EditorDropdown";
import { ToolbarButton } from "./ToolbarButton";
import { normalizeUrl } from "../../../utils/urlUtils";
import { createPluginsFromConfig } from "../config/pluginFactory";
import { htmlToSlate, slateToHtml } from "../utils/conversion";
import { createKeyboardHandler, generateInternalLinkUrl, isHotkey, } from "../utils/plugins";
import { cn } from "../../../../lib/utils";
import { useCachedSimplifiedProfile } from "../hooks/useProfileCache";
import { getRichTextAlignmentIcon, getRichTextHorizontalRuleIcon, getRichTextLinkIcon, getRichTextListIcon, getRichTextMarkIcon, getRichTextStripFormattingIcon, } from "../richTextToolbarIcons";
// Memoized toolbar button component using useSlateSelector for performance
const ToolbarButtonWrapper = memo(({ option, icon, onMouseDown }) => {
    // Use useSlateSelector to only re-render when the active state changes
    const isActive = useSlateSelector((editor) => {
        switch (option.type) {
            case "mark":
                return editor.isMarkActive(option.id);
            case "block":
                return editor.isBlockActive(option.id);
            case "alignment":
                return editor.isAlignActive(SLATE_ALIGNMENTS[option.id].value);
            case "link":
                return editor.isLinkActive();
            case "list":
                const listType = option.id === "unordered-list" ? "unordered" : "ordered";
                return editor.isListActive(listType);
            case "insertion":
                return false; // Insertion buttons are never active
            default:
                return false;
        }
    });
    const isDisabled = useSlateSelector((editor) => {
        if (option.type !== "link")
            return false;
        return !editor.selection || Range.isCollapsed(editor.selection);
    });
    const renderedIcon = option.type === "link" && isActive ? (_jsx(Link2Off, { className: "text-neutral-grey-100 h-4 w-4", strokeWidth: 1, "aria-hidden": "true" })) : (icon);
    return (_jsx(ToolbarButton, { icon: renderedIcon, active: isActive, disabled: isDisabled, onMouseDown: onMouseDown, type: `${option.type}-${option.id}` }));
});
ToolbarButtonWrapper.displayName = "ToolbarButtonWrapper";
// Memoized dropdown component to prevent unnecessary re-renders
const MemoizedEditorDropdown = memo(({ options, editor, label, buttonStyle }) => {
    return (_jsx(EditorDropdown, { options: options, editor: editor, label: label, buttonStyle: buttonStyle }));
});
MemoizedEditorDropdown.displayName = "MemoizedEditorDropdown";
export const ReactSlate = forwardRef((props, ref) => {
    const { value = "", onChange, onFocus, onBlur, onMouseUp, onKeyUp, onContextMenu, readOnly = false, placeholder = "Enter some text...", profile, showControls, } = props;
    const editorProfile = profile || {
        toolbar: {
            groups: [],
        },
    };
    // Convert to simplified profile for the conversion functions (with caching)
    const simplifiedProfile = useCachedSimplifiedProfile(editorProfile);
    // Create the Slate editor with plugins - only once
    const editor = useMemo(() => {
        const baseEditor = createEditor();
        // Apply plugins in correct order
        const enhancedEditor = createPluginsFromConfig(withHistory(withReact(baseEditor)));
        // Initialize with empty paragraph - Slate's useEffect might run after our effects
        enhancedEditor.children = [
            { type: "paragraph", children: [{ text: "" }] },
        ];
        return enhancedEditor;
    }, []);
    // Track previous value to detect external changes
    const previousValueRef = useRef(value);
    const isInternalChangeRef = useRef(false);
    const isApplyingExternalValueRef = useRef(false);
    // Expose editor methods via ref for external access (e.g., AI text editing)
    useImperativeHandle(ref, () => ({
        getEditor: () => editor,
        getSelection: () => editor.selection,
        getSelectionFromDOMRange: (domRange) => {
            try {
                return ReactEditor.toSlateRange(editor, domRange, {
                    exactMatch: false,
                    suppressThrow: false,
                });
            }
            catch (error) {
                return null;
            }
        },
        replaceTextAtSelection: (selection, newText) => {
            if (selection) {
                Transforms.select(editor, selection);
                // Delete the selected text first, then insert the new text
                // This ensures reliable replacement even if the selection state has changed
                if (editor.selection &&
                    Range.isRange(editor.selection) &&
                    !Range.isCollapsed(editor.selection)) {
                    Transforms.delete(editor);
                }
                Transforms.insertText(editor, newText);
                // Manually trigger onChange to notify parent component and update DOM
                // This is necessary because Transforms operations don't automatically trigger onChange
                if (onChange) {
                    try {
                        const html = slateToHtml(editor.children, simplifiedProfile);
                        // Only trigger onChange if content actually changed
                        if (html !== value) {
                            // Mark this as an internal change to prevent the useEffect from resetting the editor
                            isInternalChangeRef.current = true;
                            onChange(html);
                        }
                    }
                    catch (error) {
                        console.warn("Failed to convert Slate to HTML in replaceTextAtSelection:", error);
                    }
                }
            }
        },
        openLinkDialog: () => {
            editor.insertLink({
                onOpenLinkDialog: (callback) => {
                    setSelectedLink({
                        type: "external",
                        url: "",
                        target: "_blank",
                    });
                    setShowLinkDialog(true);
                    setLinkDialogCallback(() => callback);
                },
            });
        },
    }), [editor, onChange, simplifiedProfile, value]);
    // Initial value for Slate - capture initial props at mount time
    const initialSlateValue = useMemo(() => {
        try {
            return htmlToSlate(value, simplifiedProfile);
        }
        catch (error) {
            console.warn("Failed to convert initial HTML to Slate:", error);
            return [{ type: "paragraph", children: [{ text: "" }] }];
        }
        // eslint-disable-next-line react-hooks/exhaustive-deps
    }, []); // Intentionally empty - we want initial value only
    // Convert HTML to Slate format - memoized for performance
    const slateValue = useMemo(() => {
        try {
            return htmlToSlate(value, simplifiedProfile);
        }
        catch (error) {
            console.warn("Failed to convert HTML to Slate:", error);
            return [{ type: "paragraph", children: [{ text: "" }] }];
        }
    }, [value, simplifiedProfile]);
    // Track if user-initiated focus should be restored after toolbar renders
    const pendingFocusRestoreRef = useRef(false);
    const previousShowControlsRef = useRef(showControls);
    // The caret position the user clicked, captured at mousedown as a *logical*
    // Slate range. When an unfocused field is clicked, the toolbar appears and
    // steals focus, which clears both editor.selection and the DOM selection by
    // the time we re-focus - so neither can be read back. Capturing from the
    // click coordinates up front, before any of that, is the only reliable
    // source. A logical range also survives the layout shift when the toolbar
    // pushes the content down.
    const pendingClickRangeRef = useRef(null);
    // Resolve viewport coordinates to a Slate range via the browser's caret
    // hit-testing (independent of focus/selection state), or null if the point
    // is outside this editor.
    const slateRangeFromPoint = useCallback((x, y) => {
        try {
            const doc = document;
            let domRange = null;
            if (typeof doc.caretRangeFromPoint === "function") {
                // Chromium / WebKit
                domRange = doc.caretRangeFromPoint(x, y);
            }
            else if (typeof doc.caretPositionFromPoint === "function") {
                // Firefox
                const pos = doc.caretPositionFromPoint(x, y);
                if (pos) {
                    domRange = document.createRange();
                    domRange.setStart(pos.offsetNode, pos.offset);
                    domRange.collapse(true);
                }
            }
            if (!domRange)
                return null;
            const editableEl = ReactEditor.toDOMNode(editor, editor);
            if (!editableEl.contains(domRange.startContainer))
                return null;
            return ReactEditor.toSlateRange(editor, domRange, {
                exactMatch: false,
                suppressThrow: true,
            });
        }
        catch {
            return null;
        }
    }, [editor]);
    // Capture the click target before focus/toolbar can disrupt the selection.
    const handleMouseDown = useCallback((event) => {
        // Multi-clicks (double = word, triple = paragraph) expand the selection
        // natively. Capturing a collapsed point here would let the delayed
        // caret-restore logic collapse that expansion again.
        if (event.detail > 1) {
            pendingClickRangeRef.current = null;
            return;
        }
        pendingClickRangeRef.current = slateRangeFromPoint(event.clientX, event.clientY);
    }, [slateRangeFromPoint]);
    const selectPendingClickRangeIfStaleCollapsed = useCallback(() => {
        const clickedRange = pendingClickRangeRef.current;
        const currentSelection = editor.selection;
        // Slate's internal selection lags behind the DOM. If the live DOM
        // selection is already expanded (double-click word selection, fast
        // drag-select), trust it instead of restoring a stale collapsed caret.
        const domSelection = window.getSelection();
        if (domSelection && !domSelection.isCollapsed) {
            return false;
        }
        const shouldUseClickedRange = clickedRange &&
            Range.isRange(clickedRange) &&
            Range.isCollapsed(clickedRange) &&
            (!currentSelection ||
                (Range.isRange(currentSelection) &&
                    Range.isCollapsed(currentSelection) &&
                    !Range.equals(currentSelection, clickedRange)));
        if (!shouldUseClickedRange) {
            return false;
        }
        try {
            ReactEditor.focus(editor);
            Transforms.select(editor, clickedRange);
            pendingClickRangeRef.current = null;
            return true;
        }
        catch {
            return false;
        }
    }, [editor]);
    // Re-focus the editor after the toolbar mounts and put the caret back where
    // the user clicked. If Slate kept a selection (e.g. a drag-select), trust it
    // and leave it alone; only fall back to the captured click point - or the end
    // of the content - when the selection was lost.
    const restoreClickedCaret = useCallback(() => {
        if (selectPendingClickRangeIfStaleCollapsed()) {
            return;
        }
        // An expanded DOM selection (double-click word selection, drag-select)
        // is newer than Slate's internal state — don't replace it with a caret.
        const domSelection = window.getSelection();
        if (domSelection && !domSelection.isCollapsed) {
            return;
        }
        if (editor.selection) {
            try {
                ReactEditor.focus(editor);
            }
            catch {
                // Silently fail if editor is not ready
            }
            return;
        }
        const clickedRange = pendingClickRangeRef.current;
        pendingClickRangeRef.current = null;
        try {
            ReactEditor.focus(editor);
            if (clickedRange) {
                Transforms.select(editor, clickedRange);
            }
            else if (editor.children.length > 0) {
                Transforms.select(editor, Editor.end(editor, []));
            }
        }
        catch {
            // Silently fail - focus is set, Slate will place the caret on keystroke
        }
    }, [editor, selectPendingClickRangeIfStaleCollapsed]);
    // Restore focus after toolbar appears (only when user-initiated via pendingFocusRestoreRef)
    useEffect(() => {
        const wasShowingControls = previousShowControlsRef.current;
        previousShowControlsRef.current = showControls;
        // If showControls just became true and we have a pending focus restore
        if (showControls &&
            !wasShowingControls &&
            pendingFocusRestoreRef.current) {
            pendingFocusRestoreRef.current = false;
            // Use a short timeout to let the toolbar render complete
            const timeoutId = setTimeout(() => {
                restoreClickedCaret();
            }, 50);
            return () => clearTimeout(timeoutId);
        }
    }, [showControls, editor, restoreClickedCaret]);
    // Update editor when value changes from outside
    useEffect(() => {
        // Skip if this is an internal change (from our own onChange)
        if (isInternalChangeRef.current) {
            isInternalChangeRef.current = false;
            previousValueRef.current = value;
            return;
        }
        // Only update if value actually changed from outside
        if (value !== previousValueRef.current) {
            previousValueRef.current = value;
            // Update editor children with new value
            const newChildren = slateValue;
            // Prevent infinite loops by temporarily removing selection
            const currentSelection = editor.selection;
            // When we apply an external value, Slate normalization can emit change events.
            // We must not propagate those back to the parent as "user edits".
            isApplyingExternalValueRef.current = true;
            // Update the editor's children
            editor.children = newChildren;
            // Normalize the editor to ensure consistency
            Editor.normalize(editor, { force: true });
            // Restore selection if it was valid, otherwise set to end
            if (currentSelection &&
                Editor.hasPath(editor, currentSelection.anchor.path)) {
                try {
                    Transforms.select(editor, currentSelection);
                }
                catch {
                    // If selection is invalid, move to end
                    Transforms.select(editor, Editor.end(editor, []));
                }
            }
            else {
                // Move to end of document
                Transforms.select(editor, Editor.end(editor, []));
            }
            // Reset AFTER the current call stack completes. Slate's onChange is triggered
            // asynchronously by React re-rendering, so we need to keep the flag true
            // until after that render cycle completes.
            setTimeout(() => {
                isApplyingExternalValueRef.current = false;
            }, 0);
        }
    }, [editor, slateValue, value]);
    // Handle value changes with proper Slate patterns
    const handleChange = useCallback((newValue) => {
        // If this change was caused by applying an external `value` prop (and normalization),
        // do not echo it back to the parent.
        if (isApplyingExternalValueRef.current) {
            return;
        }
        if (onChange) {
            try {
                const html = slateToHtml(newValue, simplifiedProfile);
                // Only trigger onChange if content actually changed
                if (html !== value) {
                    // Mark this as an internal change
                    isInternalChangeRef.current = true;
                    onChange(html);
                }
            }
            catch (error) {
                console.warn("Failed to convert Slate to HTML:", error);
            }
        }
    }, [onChange, simplifiedProfile, value]);
    const commitCurrentValue = useCallback(() => {
        if (!onChange) {
            return;
        }
        try {
            const html = slateToHtml(editor.children, simplifiedProfile);
            if (html !== value) {
                isInternalChangeRef.current = true;
                onChange(html);
            }
        }
        catch (error) {
            console.warn("Failed to convert Slate to HTML:", error);
        }
    }, [editor, onChange, simplifiedProfile, value]);
    // Link dialog state
    const [showLinkDialog, setShowLinkDialog] = useState(false);
    const [selectedLink, setSelectedLink] = useState(null);
    const [linkDialogCallback, setLinkDialogCallback] = useState(null);
    // Commit the live DOM selection into the Slate model. Slate's own
    // DOM-selection handling is throttled, so after a native multi-click
    // (double-click word selection) the model briefly holds a stale collapsed
    // caret. Any re-render in that window (e.g. selection publishing in the
    // parent triggers one) makes slate-react restore the stale caret over the
    // user's selection. Syncing the model at interaction end closes that race.
    const syncDomSelectionToEditor = useCallback(() => {
        try {
            const domSelection = window.getSelection();
            if (!domSelection || domSelection.rangeCount === 0)
                return;
            const editableEl = ReactEditor.toDOMNode(editor, editor);
            const domRange = domSelection.getRangeAt(0);
            if (!editableEl.contains(domRange.commonAncestorContainer))
                return;
            const slateRange = ReactEditor.toSlateRange(editor, domRange, {
                exactMatch: false,
                suppressThrow: true,
            });
            if (slateRange) {
                Transforms.select(editor, slateRange);
            }
        }
        catch {
            // Ignore transient DOM/Slate mapping failures; the next Slate event will sync.
        }
    }, [editor]);
    const handleLinkButtonClick = useCallback(() => {
        syncDomSelectionToEditor();
        if (!editor.selection || Range.isCollapsed(editor.selection)) {
            return;
        }
        editor.insertLink({
            onOpenLinkDialog: (callback) => {
                setSelectedLink({
                    type: "external",
                    url: "",
                    target: "_blank",
                });
                setShowLinkDialog(true);
                setLinkDialogCallback(() => callback);
            },
        });
    }, [editor, syncDomSelectionToEditor]);
    const handleStripFormattingClick = useCallback((event) => {
        event.preventDefault();
        editor.stripFormatting();
    }, [editor]);
    // Memoize option handlers to prevent creating new functions on every render
    const optionHandlers = useMemo(() => {
        const handlers = {};
        const handleOptionSelect = (option) => (editor, event) => {
            event.preventDefault();
            // A toolbar click is an explicit user edit. It can arrive immediately
            // after a new value is synchronized into Slate, before the deferred
            // external-sync flag clears; do not let that flag suppress this edit.
            isApplyingExternalValueRef.current = false;
            syncDomSelectionToEditor();
            if (!editor.selection && editor.children.length > 0) {
                Transforms.select(editor, Editor.end(editor, []));
            }
            if (option.type === "link" &&
                (!editor.selection || Range.isCollapsed(editor.selection))) {
                return;
            }
            switch (option.type) {
                case "mark":
                    editor.toggleMark(option.id);
                    break;
                case "block":
                    editor.toggleBlock(option.id);
                    break;
                case "alignment":
                    const alignConfig = SLATE_ALIGNMENTS[option.id];
                    editor.toggleAlign(alignConfig.value);
                    break;
                case "link":
                    handleLinkButtonClick();
                    break;
                case "list":
                    const listType = option.id === "unordered-list" ? "unordered" : "ordered";
                    editor.toggleList(listType);
                    break;
                case "insertion":
                    if (option.id === "horizontal-rule") {
                        editor.insertHorizontalRule();
                    }
                    break;
                default:
                    console.warn(`Unhandled option type: ${option.type}`);
            }
        };
        // Pre-create handlers for all possible options
        editorProfile.toolbar.groups.forEach((group) => {
            group.options.forEach((option) => {
                const key = `${option.type}-${option.id}`;
                handlers[key] = handleOptionSelect(option);
            });
        });
        return handlers;
    }, [
        editor,
        editorProfile.toolbar.groups,
        handleLinkButtonClick,
        syncDomSelectionToEditor,
    ]);
    // Create button handlers that adapt the signature for toolbar buttons
    const buttonHandlers = useMemo(() => {
        const handlers = {};
        Object.entries(optionHandlers).forEach(([key, handler]) => {
            handlers[key] = (event) => {
                handler(editor, event);
            };
        });
        return handlers;
    }, [optionHandlers, editor]);
    const getOption = useCallback((option) => {
        const handlerKey = `${option.type}-${option.id}`;
        switch (option.type) {
            case "mark":
                const markConfig = SLATE_MARKS[option.id];
                return {
                    id: option.id,
                    label: markConfig.label,
                    icon: getRichTextMarkIcon(option.id) ?? markConfig.icon,
                    isActive: (editor) => editor.isMarkActive(option.id),
                    toggle: optionHandlers[handlerKey],
                };
            case "block":
                const blockConfig = SLATE_BLOCKS[option.id];
                return {
                    id: option.id,
                    label: blockConfig.label,
                    icon: blockConfig.icon,
                    isActive: (editor) => editor.isBlockActive(option.id),
                    toggle: optionHandlers[handlerKey],
                };
            case "alignment":
                const alignConfig = SLATE_ALIGNMENTS[option.id];
                return {
                    id: option.id,
                    label: alignConfig.label,
                    icon: getRichTextAlignmentIcon(option.id) ?? alignConfig.icon,
                    isActive: (editor) => editor.isAlignActive(alignConfig.value),
                    toggle: optionHandlers[handlerKey],
                };
            case "link":
                return {
                    id: "link",
                    label: "Link",
                    icon: getRichTextLinkIcon(),
                    isActive: (editor) => editor.isLinkActive(),
                    toggle: optionHandlers[handlerKey],
                };
            case "list":
                return {
                    id: option.id,
                    label: option.id === "unordered-list"
                        ? "Bulleted List"
                        : "Numbered List",
                    icon: getRichTextListIcon(option.id),
                    isActive: (editor) => editor.isListActive(option.id === "unordered-list" ? "unordered" : "ordered"),
                    toggle: optionHandlers[handlerKey],
                };
            case "divider":
                return { id: "divider", label: "Divider" };
            case "insertion":
                if (option.id === "horizontal-rule") {
                    return {
                        id: option.id,
                        label: "Horizontal Rule",
                        icon: getRichTextHorizontalRuleIcon(),
                        isActive: () => false, // Insertion buttons are never "active"
                        toggle: optionHandlers[handlerKey],
                    };
                }
                return undefined;
            default:
                return undefined;
        }
    }, [optionHandlers]);
    // Memoize dropdown options creation with strict type safety
    const createDropdownOptions = useCallback((options) => {
        return options
            .map((option) => {
            const optionObj = getOption(option);
            if (!optionObj)
                return null;
            return {
                value: optionObj,
                label: optionObj.label || option.id,
                icon: optionObj.icon,
                style: optionObj.style,
                isActive: (editor) => {
                    switch (option.type) {
                        case "mark":
                            return editor.isMarkActive(option.id);
                        case "block":
                            return editor.isBlockActive(option.id);
                        case "alignment":
                            return editor.isAlignActive(SLATE_ALIGNMENTS[option.id].value);
                        case "link":
                            return editor.isLinkActive();
                        case "list":
                            const listType = option.id === "unordered-list" ? "unordered" : "ordered";
                            return editor.isListActive(listType);
                        default:
                            return false;
                    }
                },
                onSelect: optionHandlers[`${option.type}-${option.id}`],
            };
        })
            .filter(Boolean);
    }, [getOption, optionHandlers]);
    // Helper function to split options by dividers into sub-groups
    const splitOptionsByDividers = useCallback((options) => {
        const subGroups = [];
        let currentGroup = [];
        options.forEach((option) => {
            if (option.type === "divider") {
                if (currentGroup.length > 0) {
                    subGroups.push(currentGroup);
                    currentGroup = [];
                }
            }
            else {
                currentGroup.push(option);
            }
        });
        // Add the last group if it has options
        if (currentGroup.length > 0) {
            subGroups.push(currentGroup);
        }
        return subGroups;
    }, []);
    // Memoize toolbar group rendering
    const renderToolbarGroup = useCallback((group, index) => {
        const validOptions = group.options.filter((option) => getOption(option) || option.type === "divider");
        if (validOptions.length === 0)
            return null;
        // Skip rendering dropdown groups with only one option
        if (group.display === "dropdown" && validOptions.length === 1)
            return null;
        const groupStyle = {
            display: "flex",
            alignItems: "center",
            gap: "8px",
            flexWrap: "wrap",
        };
        return (_jsx("div", { style: groupStyle, children: group.display === "buttons"
                ? (() => {
                    const subGroups = splitOptionsByDividers(validOptions);
                    return subGroups.map((subGroup, subGroupIndex) => (_jsx("div", { className: "toolbar-button-group", children: subGroup.map((option) => {
                            const optionObj = getOption(option);
                            const handler = buttonHandlers[`${option.type}-${option.id}`];
                            if (!optionObj || !handler)
                                return null;
                            return (_jsx(ToolbarButtonWrapper, { option: option, icon: optionObj.icon, onMouseDown: handler }, `${option.type}-${option.id}`));
                        }) }, `subgroup-${subGroupIndex}`)));
                })()
                : (() => {
                    const dropdownOptions = createDropdownOptions(validOptions);
                    const blockOptions = validOptions.filter((option) => option.type === "block");
                    // If there's only one block option, render it as a disabled-style button
                    if (blockOptions.length === 1) {
                        const singleOption = dropdownOptions.find((opt) => blockOptions.some((blockOpt) => blockOpt.type === "block" &&
                            getOption(blockOpt) === opt.value));
                        return (_jsx("div", { className: "toolbar-dropdown-container", children: _jsx("button", { className: "toolbar-dropdown-button", disabled: true, children: group.label ? (_jsxs(_Fragment, { children: [_jsxs("span", { className: "toolbar-dropdown-content", children: [group.label, ": ", singleOption?.label] }), _jsx("span", { className: "toolbar-dropdown-arrow", children: "\u25BC" })] })) : group.showIconsOnly ? (_jsxs(_Fragment, { children: [_jsx("span", { className: "toolbar-dropdown-icon", children: singleOption?.icon }), _jsx("span", { className: "toolbar-dropdown-arrow", children: "\u25BC" })] })) : (_jsxs(_Fragment, { children: [_jsxs("span", { className: "toolbar-dropdown-icon", children: [singleOption?.icon && (_jsx("span", { className: "toolbar-dropdown-icon", children: singleOption.icon })), _jsx("span", { className: "toolbar-dropdown-content", children: singleOption?.label })] }), _jsx("span", { className: "toolbar-dropdown-arrow", children: "\u25BC" })] })) }) }));
                    }
                    // Multiple options - render normally wrapped in grey container
                    const isActive = dropdownOptions.some((option) => option.isActive(editor));
                    const buttonStyle = {
                        padding: "5px 10px",
                        margin: "0",
                        background: isActive ? "var(--color-white)" : "transparent",
                        border: "none",
                        borderRadius: "3px",
                        cursor: "pointer",
                        boxShadow: isActive
                            ? "0 1px 2px color-mix(in srgb, var(--color-neutral-grey-100) 10%, transparent)"
                            : "none",
                    };
                    return (_jsx("div", { className: "toolbar-dropdown-container", children: _jsx(MemoizedEditorDropdown, { options: dropdownOptions, editor: editor, label: group.label, buttonStyle: buttonStyle }) }));
                })() }, `group-${group.id || index}`));
    }, [
        getOption,
        splitOptionsByDividers,
        createDropdownOptions,
        optionHandlers,
        buttonHandlers,
        editor,
    ]);
    // Memoize toolbar structure (expensive grouping operation)
    const toolbarStructure = useMemo(() => {
        // Group toolbar items by row
        const groupsByRow = editorProfile.toolbar.groups.reduce((acc, group, index) => {
            const row = group.row !== undefined ? group.row : index;
            if (!acc[row])
                acc[row] = [];
            acc[row].push(group);
            return acc;
        }, {});
        // Return sorted entries for consistent rendering
        return Object.entries(groupsByRow).sort(([a], [b]) => parseInt(a) - parseInt(b));
    }, [editorProfile.toolbar.groups]);
    const editLink = useCallback((element) => {
        const linkType = element.link?.type || "external";
        let linkData;
        if (linkType === "internal") {
            linkData = {
                type: "internal",
                itemId: element.link?.itemId ||
                    element.link?.targetItemLongId?.split("/").pop() ||
                    "",
                targetItemLongId: element.link?.targetItemLongId || "",
                target: element.link?.target || "",
                queryString: element.link?.queryString || "",
                title: element.link?.title || "",
                nofollow: !!element.link?.nofollow,
            };
        }
        else {
            linkData = {
                type: "external",
                url: element.url || element.link?.url || "",
                target: element.link?.target || "_blank",
                queryString: element.link?.queryString || "",
                title: element.link?.title || "",
                nofollow: !!element.link?.nofollow,
            };
        }
        setSelectedLink(linkData);
        setShowLinkDialog(true);
    }, []);
    const handleLinkUpdate = useCallback((link) => {
        if (linkDialogCallback) {
            linkDialogCallback(link);
            commitCurrentValue();
            setLinkDialogCallback(null);
        }
        else {
            const [linkNode] = Editor.nodes(editor, {
                match: (n) => !Editor.isEditor(n) && Element.isElement(n) && n.type === "link",
            });
            if (linkNode) {
                const [node, path] = linkNode;
                let newProperties;
                if (link.type === "internal") {
                    newProperties = {
                        url: "",
                        link: {
                            type: "internal",
                            targetItemLongId: link.targetItemLongId,
                            itemId: link.itemId,
                            target: link.target,
                            queryString: link.queryString,
                            title: link.title,
                            nofollow: link.nofollow,
                        },
                    };
                }
                else {
                    const normalizedUrl = normalizeUrl(link.url || "");
                    newProperties = {
                        url: normalizedUrl,
                        link: {
                            type: "external",
                            url: normalizedUrl,
                            target: link.target,
                            queryString: link.queryString,
                            title: link.title,
                            nofollow: link.nofollow,
                        },
                    };
                }
                Transforms.setNodes(editor, newProperties, { at: path });
                // Move cursor after the link
                const after = Editor.after(editor, path);
                if (after) {
                    Transforms.select(editor, after);
                }
                ReactEditor.focus(editor);
                commitCurrentValue();
            }
        }
        setShowLinkDialog(false);
        setSelectedLink(null);
    }, [commitCurrentValue, editor, linkDialogCallback]);
    const baseKeyboardHandler = useMemo(() => createKeyboardHandler(editor, simplifiedProfile), [editor, simplifiedProfile]);
    const handleKeyDown = useCallback((event) => {
        if (isHotkey("mod+k", event)) {
            event.preventDefault();
            handleLinkButtonClick();
            return;
        }
        baseKeyboardHandler(event);
    }, [baseKeyboardHandler, handleLinkButtonClick]);
    // Handle focus - restore focus if toolbar just appeared and stole it
    const handleFocus = useCallback(() => {
        // If controls are not showing yet, mark that we need to restore focus after they appear
        if (!showControls && !readOnly) {
            pendingFocusRestoreRef.current = true;
        }
        else if (showControls && !readOnly) {
            // Controls are already showing - user clicked but toolbar render may have stolen focus
            // Use setTimeout to allow editor to fully initialize after re-render
            setTimeout(() => {
                restoreClickedCaret();
            }, 100);
        }
        selectPendingClickRangeIfStaleCollapsed();
        // Call the original onFocus handler
        onFocus?.();
    }, [
        showControls,
        readOnly,
        onFocus,
        restoreClickedCaret,
        selectPendingClickRangeIfStaleCollapsed,
    ]);
    // Handle blur
    const handleBlur = useCallback(() => {
        onBlur?.();
    }, [onBlur]);
    const handleMouseUp = useCallback(() => {
        syncDomSelectionToEditor();
        onMouseUp?.();
    }, [onMouseUp, syncDomSelectionToEditor]);
    const handleDoubleClick = useCallback(() => {
        syncDomSelectionToEditor();
    }, [syncDomSelectionToEditor]);
    const handleKeyUp = useCallback(() => {
        onKeyUp?.();
    }, [onKeyUp]);
    // Calculate proper list numbering for ordered lists
    const calculateListNumbers = useCallback((elements) => {
        const numberMap = new Map();
        const counters = [0, 0, 0, 0, 0, 0]; // Support up to 6 levels
        let wasInList = false; // Track if previous element was a list item
        elements.forEach((element, index) => {
            if (Element.isElement(element) &&
                element.type === "list-item" &&
                element.listType === "ordered") {
                const indent = element.indent || 0;
                // Ensure indent is within bounds
                if (indent < 0 || indent >= counters.length)
                    return;
                // If we weren't in a list before, this is a new list - reset all counters
                if (!wasInList) {
                    for (let i = 0; i < counters.length; i++) {
                        counters[i] = 0;
                    }
                }
                // Increment counter for current level
                counters[indent] = (counters[indent] || 0) + 1;
                // Reset all deeper level counters
                for (let i = indent + 1; i < counters.length; i++) {
                    counters[i] = 0;
                }
                // Build number string for current level only (e.g., "3.")
                const numberString = counters[indent] + ".";
                numberMap.set(`${index}`, numberString);
                wasInList = true;
            }
            else {
                // Not a list item, so we're no longer in a list
                wasInList = false;
            }
        });
        return numberMap;
    }, []);
    // Calculate list numbers for the entire editor content (memoized for performance)
    const listNumbers = useMemo(() => {
        return calculateListNumbers(editor.children);
    }, [editor.children, calculateListNumbers]);
    // Memoize renderElement to prevent unnecessary re-renders as per Slate performance docs
    const renderElement = useCallback(({ attributes, children, element }) => {
        const style = {
            textAlign: element.align || "left",
        };
        if (element.type === "link") {
            const isInternal = element.link?.type === "internal";
            const url = isInternal
                ? generateInternalLinkUrl(element.link?.itemId, element.link?.targetItemLongId, element.link?.queryString)
                : normalizeUrl(element.url || element.link?.url || "#");
            return (_jsx("a", { ...attributes, href: url, style: style, className: `slate-link ${isInternal ? "internal-link" : "external-link"}`, onClick: (e) => {
                    if (!readOnly) {
                        e.preventDefault();
                        editLink(element);
                    }
                }, children: children }));
        }
        if (element.type === "list-item") {
            const indent = element.indent || 0;
            const listType = element.listType || "unordered";
            const listStyle = {
                ...style,
                position: "relative",
                listStyleType: "none",
            };
            // Find the element's index in the editor children to get the correct number
            const elementIndex = editor.children.findIndex((child) => child === element);
            const listNumber = listNumbers.get(`${elementIndex}`) || "";
            return (_jsxs("div", { ...attributes, style: listStyle, className: `slate-list-item slate-list-${listType}`, "data-indent": indent, "data-list-number": listNumber, children: [_jsx("span", { className: "slate-list-bullet" }), _jsx("div", { className: "slate-list-content", children: children })] }));
        }
        if (element.type === "horizontal-rule") {
            return (_jsxs("div", { ...attributes, contentEditable: false, style: { ...style, userSelect: "none" }, children: [children, _jsx("hr", { style: {
                            border: "none",
                            borderTop: "1px solid var(--color-border-default)",
                            margin: "1em 0",
                            width: "100%",
                        } })] }));
        }
        if (element.type === "line-break") {
            return (_jsxs("span", { ...attributes, contentEditable: false, children: [children, _jsx("br", {})] }));
        }
        // Handle different block types using built-in SLATE_BLOCKS configuration
        const isValidBlockId = element.type in SLATE_BLOCKS;
        const blockConfig = isValidBlockId
            ? SLATE_BLOCKS[element.type]
            : undefined;
        if (blockConfig && element.type === "no-tag") {
            // Special handling for no-tag blocks (plain text without wrapper)
            return (_jsx("div", { ...attributes, style: style, children: children }));
        }
        // For standard blocks, use the appropriate HTML tag
        if (blockConfig) {
            const tagName = blockConfig.htmlTag;
            return React.createElement(tagName, { ...attributes, style }, children);
        }
        // Default fallback to paragraph
        return (_jsx("p", { ...attributes, style: style, children: children }));
    }, [readOnly, editLink, editor.children, listNumbers]);
    // Memoize renderLeaf to prevent unnecessary re-renders as per Slate performance docs
    const renderLeaf = useCallback(({ attributes, children, leaf }) => {
        let el = _jsx("span", { ...attributes, children: children });
        // Apply marks using the built-in SLATE_MARKS configuration
        // Iterate over all possible marks to preserve existing formatting
        Object.keys(SLATE_MARKS).forEach((markId) => {
            if (leaf[markId]) {
                const markConfig = SLATE_MARKS[markId];
                if (markConfig) {
                    if (markId === "extrabold") {
                        // Special handling for extrabold with CSS class
                        el = _jsx("span", { className: "extrabold", children: el });
                    }
                    else {
                        // Standard HTML tag rendering
                        const tagName = markConfig.htmlTag;
                        el = React.createElement(tagName, {}, el);
                    }
                }
            }
        });
        return el;
    }, []);
    // Memoize renderPlaceholder for consistency
    const renderPlaceholder = useCallback(({ attributes, children }) => (_jsx("span", { ...attributes, className: "text-neutral-grey-50 p-2", children: children })), []);
    return (_jsxs("div", { className: `slate-editor ${props.className && props.className}`, children: [_jsxs(Slate, { editor: editor, initialValue: initialSlateValue, onChange: handleChange, children: [!readOnly && showControls && (_jsx("div", { className: "rte-toolbar-reveal", children: _jsxs("div", { className: "rte-toolbar flex flex-col gap-2 pb-4", children: [toolbarStructure.map(([rowIndex, rowGroups], mapIndex) => (_jsxs("div", { className: "toolbar-row", "data-testid": `rte-toolbar-row-${rowGroups[0]?.options?.[0]?.id || "unknown"}`, children: [rowGroups.map((group) => renderToolbarGroup(group, parseInt(rowIndex))), mapIndex === toolbarStructure.length - 1 && (_jsx("div", { className: "toolbar-button-group", children: _jsx(ToolbarButton, { icon: getRichTextStripFormattingIcon(), active: false, onMouseDown: handleStripFormattingClick, type: "strip-formatting" }) }))] }, `row-${rowIndex}`))), toolbarStructure.length === 0 && (_jsx("div", { className: "toolbar-row", children: _jsx("div", { className: "toolbar-button-group", children: _jsx(ToolbarButton, { icon: getRichTextStripFormattingIcon(), active: false, onMouseDown: handleStripFormattingClick, type: "strip-formatting" }) }) }))] }) })), _jsx(Editable, { "data-testid": "rte-editable", className: cn("border-border-default min-h-7 rounded-[4px] border p-1.5", "focus:border-neutral-grey-100", readOnly ? "bg-neutral-grey-5" : "bg-neutral-grey-5", "focus-shadow", "slate-editable", 
                        // MainLayout disables text selection app-wide (select-none);
                        // contenteditable content must re-enable it for mouse selection
                        // (double-click word select, drag select) to work.
                        "select-text"), readOnly: readOnly, renderPlaceholder: renderPlaceholder, onFocus: handleFocus, onBlur: handleBlur, onMouseDown: handleMouseDown, onMouseUp: handleMouseUp, onDoubleClick: handleDoubleClick, onKeyUp: handleKeyUp, onContextMenu: onContextMenu, onKeyDown: handleKeyDown, renderElement: renderElement, renderLeaf: renderLeaf })] }), showLinkDialog && selectedLink && (_jsx(LinkEditorDialog, { linkValue: selectedLink, onOk: handleLinkUpdate, onCancel: () => {
                    setShowLinkDialog(false);
                    setSelectedLink(null);
                    setLinkDialogCallback(null);
                }, "data-testid": "link-editor-dialog" }))] }));
});
ReactSlate.displayName = "ReactSlate";
export default ReactSlate;
//# sourceMappingURL=ReactSlate.js.map