// Data attribute used to identify mention nodes in the DOM
const MENTION_ATTR = "data-mention-id";
/**
 * Serialize segments to a plain text string.
 * Mentions become `trigger + label` (e.g. "@Sarah Wilson").
 */
const serializeSegments = (segments) => {
    return segments
        .map((seg) => seg.type === "text" ? seg.text : `${seg.data.trigger}${seg.data.label}`)
        .join("");
};
/**
 * Serialize segments but replace mention content with a neutral placeholder.
 * This prevents `detectActiveMention` from matching trigger characters
 * that belong to already-resolved mentions.
 */
const serializeSegmentsMasked = (segments) => {
    return segments
        .map((seg) => seg.type === "text"
        ? seg.text
        : "\uFFFC".repeat(seg.data.trigger.length + seg.data.label.length))
        .join("");
};
/**
 * Merge adjacent text segments and ensure the array starts/ends with text.
 * Returns a new array (never mutates input).
 */
const normalizeSegments = (segments) => {
    if (segments.length === 0)
        return [{ type: "text", text: "" }];
    const result = [];
    for (const seg of segments) {
        if (seg.type === "text" &&
            result.length > 0 &&
            result[result.length - 1].type === "text") {
            // Merge adjacent text segments
            result[result.length - 1].text += seg.text;
        }
        else {
            // Clone to avoid mutating input
            result.push(seg.type === "text"
                ? { type: "text", text: seg.text }
                : { type: "mention", data: { ...seg.data } });
        }
    }
    // Ensure text bookends
    if (result[0].type !== "text") {
        result.unshift({ type: "text", text: "" });
    }
    if (result[result.length - 1].type !== "text") {
        result.push({ type: "text", text: "" });
    }
    return result;
};
/**
 * Insert a mention into the segments array at the given plain-text position.
 * `startIndex` is the position of the trigger character.
 * `replaceLength` includes the trigger + query length.
 */
const insertMentionIntoSegments = (segments, mention, startIndex, replaceLength) => {
    const newSegments = [];
    let offset = 0;
    for (const seg of segments) {
        if (seg.type === "mention") {
            const mentionText = `${seg.data.trigger}${seg.data.label}`;
            const segLen = mentionText.length;
            if (offset + segLen <= startIndex ||
                offset >= startIndex + replaceLength) {
                newSegments.push({ type: "mention", data: { ...seg.data } });
            }
            // If the mention overlaps with replacement range, skip it
            offset += segLen;
        }
        else {
            const segLen = seg.text.length;
            const segStart = offset;
            const segEnd = offset + segLen;
            if (segEnd <= startIndex || segStart >= startIndex + replaceLength) {
                // Entirely outside the replacement range
                newSegments.push({ type: "text", text: seg.text });
            }
            else {
                // This text segment overlaps the replacement range
                const beforeStart = Math.max(0, startIndex - segStart);
                const afterStart = Math.min(segLen, startIndex + replaceLength - segStart);
                const before = seg.text.slice(0, beforeStart);
                const after = seg.text.slice(afterStart);
                if (before) {
                    newSegments.push({ type: "text", text: before });
                }
                newSegments.push({ type: "mention", data: mention });
                // Add a trailing space after the mention
                newSegments.push({ type: "text", text: " " + after });
            }
            offset += segLen;
        }
    }
    return normalizeSegments(newSegments);
};
/**
 * Get the plain text offset of the current caret position within a
 * contentEditable container. Mention nodes count as their trigger+label length.
 */
const getPlainTextOffset = (container) => {
    const sel = window.getSelection();
    if (!sel || sel.rangeCount === 0)
        return null;
    const range = sel.getRangeAt(0);
    if (!container.contains(range.startContainer))
        return null;
    let offset = 0;
    const walker = document.createTreeWalker(container, NodeFilter.SHOW_TEXT | NodeFilter.SHOW_ELEMENT, {
        acceptNode: (node) => {
            if (node.nodeType === Node.TEXT_NODE)
                return NodeFilter.FILTER_ACCEPT;
            if (node.nodeType === Node.ELEMENT_NODE &&
                node.hasAttribute(MENTION_ATTR)) {
                return NodeFilter.FILTER_ACCEPT;
            }
            // Skip non-mention element nodes but traverse their children
            return NodeFilter.FILTER_SKIP;
        },
    });
    let node;
    while ((node = walker.nextNode())) {
        if (node === range.startContainer) {
            // We're in a text node that contains the caret
            return offset + range.startOffset;
        }
        if (node.nodeType === Node.TEXT_NODE) {
            // Check if the range start is within a parent that is this text node's parent
            if (node.parentNode === range.startContainer) {
                // Range.startContainer is the parent element, startOffset is child index
                // Check if this text node comes before the caret child index
                const children = Array.from(range.startContainer.childNodes);
                const nodeIndex = children.indexOf(node);
                if (nodeIndex < range.startOffset) {
                    offset += (node.textContent || "").length;
                }
                else {
                    return offset;
                }
            }
            else {
                offset += (node.textContent || "").length;
            }
        }
        else if (node.nodeType === Node.ELEMENT_NODE &&
            node.hasAttribute(MENTION_ATTR)) {
            // Check if caret is right after this mention element
            if (range.startContainer === container ||
                range.startContainer === node.parentNode) {
                const children = Array.from(range.startContainer.childNodes);
                const nodeIndex = children.indexOf(node);
                if (nodeIndex < range.startOffset) {
                    node.getAttribute(MENTION_ATTR) || "";
                    const label = node.getAttribute("data-mention-label") || "";
                    const trigger = node.getAttribute("data-mention-trigger") || "";
                    offset += trigger.length + label.length;
                }
                else {
                    return offset;
                }
            }
            else {
                const trigger = node.getAttribute("data-mention-trigger") || "";
                const label = node.getAttribute("data-mention-label") || "";
                offset += trigger.length + label.length;
            }
        }
    }
    // Caret might be at the very end
    if (range.startContainer === container) {
        return offset;
    }
    return offset;
};
/**
 * Parse the DOM of a contentEditable container back into segments.
 * Text nodes become TextSegments, elements with MENTION_ATTR become MentionSegments.
 */
const parseDOMToSegments = (container, mentionMap) => {
    const segments = [];
    const processNode = (node) => {
        if (node.nodeType === Node.TEXT_NODE) {
            const text = node.textContent || "";
            if (text) {
                segments.push({ type: "text", text });
            }
        }
        else if (node.nodeType === Node.ELEMENT_NODE) {
            const el = node;
            if (el.hasAttribute(MENTION_ATTR)) {
                const id = el.getAttribute(MENTION_ATTR) || "";
                const data = mentionMap.get(id);
                if (data) {
                    segments.push({ type: "mention", data: { ...data } });
                }
                // Don't recurse into mention nodes
                return;
            }
            // Handle <br> as newline
            if (el.tagName === "BR") {
                segments.push({ type: "text", text: "\n" });
                return;
            }
            // Handle block-level elements that create newlines (div, p)
            const isBlock = el.tagName === "DIV" || el.tagName === "P";
            if (isBlock && segments.length > 0) {
                // Add newline before block content (if not first)
                const lastSeg = segments[segments.length - 1];
                if (lastSeg.type === "text" && !lastSeg.text.endsWith("\n")) {
                    segments.push({ type: "text", text: "\n" });
                }
            }
            // Recurse into children
            for (const child of Array.from(el.childNodes)) {
                processNode(child);
            }
        }
    };
    for (const child of Array.from(container.childNodes)) {
        processNode(child);
    }
    return normalizeSegments(segments);
};
/**
 * Set the caret at a specific plain-text offset within a contentEditable container.
 */
const setCaretAtOffset = (container, targetOffset) => {
    const sel = window.getSelection();
    if (!sel)
        return;
    let remaining = targetOffset;
    const walker = document.createTreeWalker(container, NodeFilter.SHOW_TEXT | NodeFilter.SHOW_ELEMENT, {
        acceptNode: (node) => {
            if (node.nodeType === Node.TEXT_NODE)
                return NodeFilter.FILTER_ACCEPT;
            if (node.nodeType === Node.ELEMENT_NODE &&
                node.hasAttribute(MENTION_ATTR)) {
                return NodeFilter.FILTER_ACCEPT;
            }
            return NodeFilter.FILTER_SKIP;
        },
    });
    let node;
    while ((node = walker.nextNode())) {
        if (node.nodeType === Node.TEXT_NODE) {
            const len = (node.textContent || "").length;
            if (remaining <= len) {
                const range = document.createRange();
                range.setStart(node, remaining);
                range.collapse(true);
                sel.removeAllRanges();
                sel.addRange(range);
                return;
            }
            remaining -= len;
        }
        else if (node.nodeType === Node.ELEMENT_NODE &&
            node.hasAttribute(MENTION_ATTR)) {
            const trigger = node.getAttribute("data-mention-trigger") || "";
            const label = node.getAttribute("data-mention-label") || "";
            const mentionLen = trigger.length + label.length;
            if (remaining <= mentionLen) {
                // Place caret after this mention
                const range = document.createRange();
                range.setStartAfter(node);
                range.collapse(true);
                sel.removeAllRanges();
                sel.addRange(range);
                return;
            }
            remaining -= mentionLen;
        }
    }
    // If we didn't place the caret, put it at the end
    const range = document.createRange();
    range.selectNodeContents(container);
    range.collapse(false);
    sel.removeAllRanges();
    sel.addRange(range);
};

export { MENTION_ATTR, getPlainTextOffset, insertMentionIntoSegments, normalizeSegments, parseDOMToSegments, serializeSegments, serializeSegmentsMasked, setCaretAtOffset };
