import { $copyBlockFormatIndent, $getSelectionStyleValueForProperty, $patchStyleText, $setBlocksType, getCSSFromStyleObject, getStyleObjectFromCSS, } from '@lexical/selection';
import React from 'react';
import { $createHeadingNode, $createQuoteNode, $isHeadingNode, } from '@lexical/rich-text';
import { $isListItemNode, $removeList } from '@lexical/list';
import { $findMatchingParent } from '@lexical/utils';
import { $createParagraphNode, $getSelection, $isElementNode, $isRangeSelection, } from 'lexical';
export const DEFAULT_HEADING_FONT_SIZES = {
    h1: '34px',
    h2: '29px',
    h3: '24px',
    h4: '21px',
    h5: '18px',
    h6: '16px',
};
const LIST_HEADING_STYLE_PROPERTY = '--phx-list-heading';
const listItemBlockTypeByKey = new Map();
/**
 * Checks whether a style value is a supported heading tag.
 * @param value Style value to test.
 * @returns True when the value is a heading tag.
 */
function isHeadingTag(value) {
    return value !== undefined && value in DEFAULT_HEADING_FONT_SIZES;
}
/**
 * Stores block metadata for a list item during the current editor session.
 * @param listItemKey List item node key.
 * @param headingTag Heading type to store, or null to mark the list item as normal text.
 */
export function setListItemHeadingTag(listItemKey, headingTag) {
    if (headingTag === null) {
        listItemBlockTypeByKey.set(listItemKey, 'paragraph');
    }
    else {
        listItemBlockTypeByKey.set(listItemKey, headingTag);
    }
}
/**
 * Applies the heading font size to every text node inside an element.
 * @param node Element whose text descendants should receive the font size.
 * @param defaultFontSize Font size to write into each text node style.
 */
function applyHeadingFontSize(node, defaultFontSize) {
    for (const textNode of node.getAllTextNodes()) {
        const styles = getStyleObjectFromCSS(textNode.getStyle());
        textNode.setStyle(getCSSFromStyleObject({
            ...styles,
            'font-size': defaultFontSize,
        }));
    }
}
/**
 * Copies block format metadata to the replacement block and applies a font size to moved text nodes.
 * @param prevNode Block node being replaced.
 * @param nextNode Replacement block node.
 * @param defaultFontSize Font size to write into text descendants.
 */
function copyBlockFormatAndApplyFontSize(prevNode, nextNode, defaultFontSize) {
    $copyBlockFormatIndent(prevNode, nextNode);
    applyHeadingFontSize(prevNode, defaultFontSize);
}
/**
 * Finds the list item that owns a node.
 * @param node Node inside or equal to a list item.
 * @returns Matching list item, or null when the node is not in a list item.
 */
function getListItemNode(node) {
    return $isListItemNode(node) ? node : $findMatchingParent(node, (parent) => $isListItemNode(parent));
}
/**
 * Gets every list item touched by a range selection.
 * @param selection Current range selection.
 * @returns Unique selected list items.
 */
export function getSelectedListItems(selection) {
    const listItemNodes = new Set();
    const selectedNodes = [selection.anchor.getNode(), selection.focus.getNode(), ...selection.getNodes()];
    /**
     * Gets every list item touched by a range selection.
     * @param node LexicalNode.
     * @returns Unique selected list items.
     */
    const addListItems = (node) => {
        const listItemNode = getListItemNode(node);
        if (listItemNode !== null) {
            listItemNodes.add(listItemNode);
            return;
        }
        if ($isElementNode(node)) {
            for (const child of node.getChildren()) {
                addListItems(child);
            }
        }
    };
    for (const node of selectedNodes) {
        addListItems(node);
    }
    return listItemNodes;
}
/**
 * Finds the heading that owns a node.
 * @param node Node inside or equal to a heading.
 * @returns Matching heading, or null when the node is not in a heading.
 */
function getHeadingNode(node) {
    return $isHeadingNode(node) ? node : $findMatchingParent(node, (parent) => $isHeadingNode(parent));
}
/**
 * Gets the heading tag stored on a list item style.
 * @param style List item text style.
 * @returns Matching heading tag, or null when the style is normal text.
 */
export function getListItemHeadingTagFromStyle(style) {
    const styleObject = getStyleObjectFromCSS(style);
    const storedHeadingTag = styleObject[LIST_HEADING_STYLE_PROPERTY];
    if (isHeadingTag(storedHeadingTag)) {
        return storedHeadingTag;
    }
    const listItemFontSize = styleObject['font-size'];
    const headingEntry = Object.entries(DEFAULT_HEADING_FONT_SIZES).find(([, fontSize]) => fontSize === listItemFontSize);
    return headingEntry !== undefined ? headingEntry[0] : null;
}
/**
 * Gets the block type stored for a list item.
 * @param listItemNode List item whose block metadata should be read.
 * @returns Matching block type, or null when no metadata can be inferred.
 */
export function getListItemBlockType(listItemNode) {
    const storedBlockType = listItemBlockTypeByKey.get(listItemNode.getKey());
    if (storedBlockType !== undefined) {
        return storedBlockType;
    }
    return getListItemHeadingTagFromStyle(listItemNode.getTextStyle());
}
/**
 * Gets the heading type stored for a list item.
 * @param listItemNode List item whose heading metadata should be read.
 * @returns Matching heading tag, or null when the list item is normal text.
 */
export function getListItemHeadingTag(listItemNode) {
    const blockType = getListItemBlockType(listItemNode);
    return blockType === 'paragraph' ? null : blockType;
}
/**
 * Checks whether the current range is only part of one top-level block.
 * @param selection Current range selection.
 * @returns True when the selection should be formatted inline instead of changing the block type.
 */
function isPartialSelectionInsideSingleBlock(selection) {
    if (selection.isCollapsed()) {
        return false;
    }
    const anchorBlock = selection.anchor.getNode().getTopLevelElement();
    const focusBlock = selection.focus.getNode().getTopLevelElement();
    if (anchorBlock === null || focusBlock === null || !anchorBlock.is(focusBlock)) {
        return false;
    }
    return selection.getTextContent().length < anchorBlock.getTextContent().length;
}
/**
 * Checks whether the current selection contains list items.
 * List items must always use list metadata instead of inline-only styling.
 * @param selection Current range selection.
 * @returns True when at least one selected node belongs to a list item.
 */
function hasSelectedListItems(selection) {
    return getSelectedListItems(selection).size > 0;
}
/**
 * Gets the heading tag represented by the current selected list item font size.
 * @param editor Active Lexical editor.
 * @returns Matching heading tag, or null when the selected list item is normal text.
 */
export function getSelectedListHeadingTag(editor) {
    let selectedHeadingTag = null;
    editor.getEditorState().read(() => {
        const selection = $getSelection();
        if (!$isRangeSelection(selection)) {
            return;
        }
        const selectedNodes = [selection.anchor.getNode(), selection.focus.getNode(), ...selection.getNodes()];
        for (const node of selectedNodes) {
            const listItemNode = getListItemNode(node);
            if (listItemNode === null) {
                continue;
            }
            const headingTag = getListItemHeadingTag(listItemNode);
            if (headingTag !== null) {
                selectedHeadingTag = headingTag;
                return;
            }
        }
    });
    return selectedHeadingTag;
}
/**
 * Gets the element alignment format stored on the selected list item.
 * @param editor Active Lexical editor.
 * @returns Selected list item format, or null when the selection is not in a list item.
 */
export function getSelectedListElementFormat(editor) {
    let selectedElementFormat = null;
    editor.getEditorState().read(() => {
        const selection = $getSelection();
        if (!$isRangeSelection(selection)) {
            return;
        }
        const selectedNodes = [selection.anchor.getNode(), selection.focus.getNode(), ...selection.getNodes()];
        for (const node of selectedNodes) {
            const listItemNode = getListItemNode(node);
            if (listItemNode !== null) {
                selectedElementFormat = listItemNode.getFormatType();
                return;
            }
        }
    });
    return selectedElementFormat;
}
/**
 * Converts the current selected blocks to a heading tag while preserving heading font size.
 * @param headingTag Heading tag to apply.
 */
export function applyHeadingTypeToSelection(headingTag) {
    const selection = $getSelection();
    if (!$isRangeSelection(selection)) {
        return;
    }
    $setBlocksType(selection, () => $createHeadingNode(headingTag), (prevNode, nextNode) => copyBlockFormatAndApplyFontSize(prevNode, nextNode, DEFAULT_HEADING_FONT_SIZES[headingTag]));
}
/**
 * Preserves block metadata before converting selected content to a list item.
 * If the selection is already inside list items, it preserves list heading metadata.
 * @param editor Active Lexical editor.
 * @returns Preserved font size and heading tag, or paragraph metadata when no heading is selected.
 */
export function preserveHeadingFontSizeForList(editor) {
    let preservedBlock = null;
    editor.update(() => {
        var _a;
        const selection = $getSelection();
        if (!$isRangeSelection(selection)) {
            return;
        }
        const selectionFontSize = $getSelectionStyleValueForProperty(selection, 'font-size', '14px');
        const selectedListItems = getSelectedListItems(selection);
        for (const listItemNode of selectedListItems) {
            const headingTag = getListItemHeadingTag(listItemNode);
            const listItemFontSize = (_a = getStyleObjectFromCSS(listItemNode.getTextStyle())['font-size']) !== null && _a !== void 0 ? _a : selectionFontSize;
            if (headingTag !== null) {
                preservedBlock = {
                    fontSize: DEFAULT_HEADING_FONT_SIZES[headingTag],
                    headingTag,
                };
                break;
            }
            preservedBlock !== null && preservedBlock !== void 0 ? preservedBlock : (preservedBlock = {
                fontSize: listItemFontSize,
                headingTag: null,
            });
        }
        const headingNodes = new Set();
        const selectedNodes = [selection.anchor.getNode(), selection.focus.getNode(), ...selection.getNodes()];
        for (const node of selectedNodes) {
            const headingNode = getHeadingNode(node);
            if (headingNode !== null) {
                headingNodes.add(headingNode);
            }
        }
        for (const headingNode of headingNodes) {
            const headingTag = headingNode.getTag();
            const headingFontSize = DEFAULT_HEADING_FONT_SIZES[headingTag];
            preservedBlock = {
                fontSize: headingFontSize,
                headingTag,
            };
            applyHeadingFontSize(headingNode, headingFontSize);
        }
        if (preservedBlock === null) {
            preservedBlock = {
                fontSize: selectionFontSize,
                headingTag: null,
            };
        }
        $patchStyleText(selection, {
            'font-size': preservedBlock.fontSize,
        });
    });
    return preservedBlock;
}
/**
 * Applies font size and optional heading metadata to selected list items.
 * @param defaultFontSize Font size to apply to the list item content.
 * @param headingTag Heading type to preserve on the list item, or null to reset to normal text.
 * @returns True when at least one list item was updated.
 */
export function applyListItemFontSize(defaultFontSize, headingTag) {
    const selection = $getSelection();
    if (!$isRangeSelection(selection)) {
        return false;
    }
    const listItemNodes = getSelectedListItems(selection);
    if (listItemNodes.size === 0) {
        return false;
    }
    for (const listItemNode of listItemNodes) {
        const listItemStyle = {
            ...getStyleObjectFromCSS(listItemNode.getTextStyle()),
            'font-size': defaultFontSize,
        };
        if (headingTag !== undefined) {
            if (headingTag === null) {
                setListItemHeadingTag(listItemNode.getKey(), null);
                delete listItemStyle[LIST_HEADING_STYLE_PROPERTY];
            }
            else {
                setListItemHeadingTag(listItemNode.getKey(), headingTag);
                listItemStyle[LIST_HEADING_STYLE_PROPERTY] = headingTag;
            }
        }
        applyHeadingFontSize(listItemNode, defaultFontSize);
        listItemNode.setTextStyle(getCSSFromStyleObject(listItemStyle));
    }
    $patchStyleText(selection, {
        'font-size': defaultFontSize,
    });
    return true;
}
export default function HeaderDropdown({ editor, value }) {
    const listHeading = [
        {
            name: 'Văn bản',
            value: 'paragraph',
        },
        {
            name: 'Tiêu đề 1',
            value: 'h1',
        },
        {
            name: 'Tiêu đề 2',
            value: 'h2',
        },
        {
            name: 'Tiêu đề 3',
            value: 'h3',
        },
        {
            name: 'Tiêu đề 4',
            value: 'h4',
        },
        {
            name: 'Quote',
            value: 'quote',
        },
    ];
    /**
     * Applies a paragraph, quote, or heading choice to the current editor selection.
     * @param nextValue Selected dropdown value.
     */
    const handleChangeFont = (nextValue) => {
        if (nextValue === 'paragraph') {
            editor.update(() => {
                const selection = $getSelection();
                if (!$isRangeSelection(selection)) {
                    return;
                }
                /**
                 * List items must preserve metadata so the heading dropdown
                 * can correctly detect the current heading type.
                 */
                if (hasSelectedListItems(selection)) {
                    applyListItemFontSize('14px', null);
                    return;
                }
                if (isPartialSelectionInsideSingleBlock(selection)) {
                    $patchStyleText(selection, {
                        'font-size': '14px',
                    });
                    return;
                }
                $setBlocksType(selection, () => $createParagraphNode(), (prevNode, nextNode) => copyBlockFormatAndApplyFontSize(prevNode, nextNode, '14px'));
                const nextSelection = $getSelection();
                if ($isRangeSelection(nextSelection)) {
                    $patchStyleText(nextSelection, {
                        'font-size': '14px',
                    });
                }
            });
            return;
        }
        if (nextValue === 'quote') {
            editor.update(() => {
                const selection = $getSelection();
                if (!$isRangeSelection(selection)) {
                    return;
                }
                if (hasSelectedListItems(selection)) {
                    $removeList();
                    const nextSelection = $getSelection();
                    if ($isRangeSelection(nextSelection)) {
                        $setBlocksType(nextSelection, () => $createQuoteNode());
                    }
                    return;
                }
                $setBlocksType(selection, () => $createQuoteNode());
            });
            return;
        }
        editor.update(() => {
            const selection = $getSelection();
            const headingTag = nextValue;
            if (!$isRangeSelection(selection)) {
                return;
            }
            /**
             * List items must use metadata-based heading updates
             * instead of inline-only font-size styling.
             */
            if (hasSelectedListItems(selection)) {
                applyListItemFontSize(DEFAULT_HEADING_FONT_SIZES[headingTag], headingTag);
                return;
            }
            if (isPartialSelectionInsideSingleBlock(selection)) {
                $patchStyleText(selection, {
                    'font-size': DEFAULT_HEADING_FONT_SIZES[headingTag],
                });
                return;
            }
            $setBlocksType(selection, () => $createHeadingNode(headingTag), (prevNode, nextNode) => copyBlockFormatAndApplyFontSize(prevNode, nextNode, DEFAULT_HEADING_FONT_SIZES[headingTag]));
        });
    };
    return (React.createElement("div", { className: 'flex items-center gap-x-2' },
        React.createElement("select", { onChange: (e) => {
                handleChangeFont(e.target.value);
            }, className: 'w-24 text-xs leading-5 focus:ring-0 bg-transparent border-none rounded-md py-1 px-2 duration-200 cursor-pointer hover:bg-gray-200', value: value }, listHeading.map((heading) => {
            return (React.createElement("option", { key: heading.value, value: heading.value }, heading.name));
        })),
        React.createElement("div", { className: 'w-[1px] h-5 bg-gray-300' })));
}
//# sourceMappingURL=heading.js.map