const WORD_CHAR_REGEX = /[\p{L}\p{N}_'-]/u;
const WHITESPACE_REGEX = /\s/;
const SENTENCE_END_REGEX = /[.!?]/;
export function isWordChar(char) {
    return WORD_CHAR_REGEX.test(char);
}
export function isWhitespaceChar(char) {
    return WHITESPACE_REGEX.test(char);
}
export function isCaretOnWhitespace(text, caret) {
    const clampedCaret = Math.max(0, Math.min(caret, text.length));
    const charBefore = clampedCaret > 0 ? text[clampedCaret - 1] : "";
    const charAt = clampedCaret < text.length ? text[clampedCaret] : "";
    if (charBefore && isWordChar(charBefore) && charAt && isWordChar(charAt)) {
        return false;
    }
    if (charBefore && isWordChar(charBefore) && !charAt) {
        return false;
    }
    if (!charBefore && charAt && isWordChar(charAt)) {
        return false;
    }
    return ((charBefore !== "" && isWhitespaceChar(charBefore)) ||
        (charAt !== "" && isWhitespaceChar(charAt)) ||
        text.length === 0);
}
export function findWordBounds(text, caret) {
    const clampedCaret = Math.max(0, Math.min(caret, text.length));
    let start = clampedCaret;
    let end = clampedCaret;
    if (start < text.length &&
        isWordChar(text[start]) &&
        start > 0 &&
        isWordChar(text[start - 1])) {
        // Caret is inside a word.
    }
    else if (start > 0 && isWordChar(text[start - 1])) {
        end = start;
        start -= 1;
    }
    else if (start < text.length && isWordChar(text[start])) {
        // Caret is at the start of a word.
    }
    else {
        return { start: clampedCaret, end: clampedCaret, text: "" };
    }
    while (start > 0 && isWordChar(text[start - 1])) {
        start -= 1;
    }
    while (end < text.length && isWordChar(text[end])) {
        end += 1;
    }
    return { start, end, text: text.slice(start, end) };
}
export function findSentenceBounds(text, caret) {
    const clampedCaret = Math.max(0, Math.min(caret, text.length));
    let start = 0;
    for (let index = clampedCaret - 1; index >= 0; index -= 1) {
        if (SENTENCE_END_REGEX.test(text[index])) {
            start = index + 1;
            break;
        }
    }
    while (start < text.length && isWhitespaceChar(text[start])) {
        start += 1;
    }
    let end = text.length;
    for (let index = Math.max(clampedCaret, start); index < text.length; index += 1) {
        if (SENTENCE_END_REGEX.test(text[index])) {
            end = index + 1;
            break;
        }
    }
    while (end > start && isWhitespaceChar(text[end - 1])) {
        end -= 1;
    }
    return { start, end, text: text.slice(start, end) };
}
export function expandSelectionAtCaret(text, caret) {
    const clampedCaret = Math.max(0, Math.min(caret, text.length));
    const bounds = isCaretOnWhitespace(text, clampedCaret)
        ? findSentenceBounds(text, clampedCaret)
        : findWordBounds(text, clampedCaret);
    if (!bounds.text.trim()) {
        return { start: clampedCaret, end: clampedCaret, text: "" };
    }
    return bounds;
}
export function 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 localOffset;
}
function getTextNodeAtOffset(root, offset, affinity) {
    const walker = document.createTreeWalker(root, NodeFilter.SHOW_TEXT);
    let boundaryNode = null;
    let remaining = offset;
    while (walker.nextNode()) {
        const currentNode = walker.currentNode;
        const nodeLength = currentNode.textContent?.length ?? 0;
        if (remaining < nodeLength) {
            return { node: currentNode, offset: remaining };
        }
        if (remaining === nodeLength) {
            if (affinity === "backward") {
                return { node: currentNode, offset: remaining };
            }
            boundaryNode = currentNode;
            remaining = 0;
            continue;
        }
        remaining -= nodeLength;
    }
    if (!boundaryNode) {
        return null;
    }
    return {
        node: boundaryNode,
        offset: boundaryNode.textContent?.length ?? 0,
    };
}
export function createRangeFromTextOffsets(root, start, end) {
    const startData = getTextNodeAtOffset(root, start, "forward");
    const endData = getTextNodeAtOffset(root, end, "backward");
    if (!startData || !endData) {
        return null;
    }
    try {
        const range = document.createRange();
        range.setStart(startData.node, startData.offset);
        range.setEnd(endData.node, endData.offset);
        return range;
    }
    catch {
        return null;
    }
}
export function applyExpandedSelectionToInput(element) {
    const start = element.selectionStart ?? 0;
    const end = element.selectionEnd ?? start;
    if (start !== end) {
        return null;
    }
    const bounds = expandSelectionAtCaret(element.value, start);
    if (!bounds.text) {
        return null;
    }
    element.setSelectionRange(bounds.start, bounds.end);
    element.dispatchEvent(new Event("select", { bubbles: true }));
    return bounds;
}
export function applyExpandedSelectionToContentEditable(container) {
    const selection = window.getSelection();
    if (!selection || selection.rangeCount === 0 || !selection.isCollapsed) {
        return null;
    }
    const range = selection.getRangeAt(0);
    if (!container.contains(range.commonAncestorContainer)) {
        return null;
    }
    const fullText = container.textContent || "";
    const caret = getGlobalTextOffset(container, range.startContainer, range.startOffset);
    const bounds = expandSelectionAtCaret(fullText, caret);
    if (!bounds.text) {
        return null;
    }
    const nextRange = createRangeFromTextOffsets(container, bounds.start, bounds.end);
    if (!nextRange) {
        return null;
    }
    selection.removeAllRanges();
    selection.addRange(nextRange);
    return bounds;
}
//# sourceMappingURL=expandSelectionAtCaret.js.map