import { jsx as _jsx, Fragment as _Fragment } from "react/jsx-runtime";
import { useEditContext, useFieldsEditContext } from "../client/editContext";
import { findFieldElement } from "../utils";
import { useState } from "react";
import { useEffect } from "react";
import { cn } from "../../lib/utils";
import { Lightbulb } from "lucide-react";
import * as DiffLib from "diff";
import { SuggestionDisplayPopover } from "../reviews/SuggestionDisplayPopover";
export function SuggestionHighlighting({ suggestion, scale, iframe, }) {
    const editContext = useEditContext();
    const modifiedFields = useFieldsEditContext();
    const [ranges, setRanges] = useState([]);
    const [textContent, setTextContent] = useState("");
    useEffect(() => {
        const findRanges = async () => {
            if (!editContext)
                return;
            const field = await editContext.itemsRepository.getField({
                item: {
                    id: suggestion.itemId,
                    language: suggestion.mainItemLanguage,
                    version: suggestion.mainItemVersion,
                },
                fieldId: suggestion.fieldId,
            });
            const modifiedField = modifiedFields?.modifiedFields.find((x) => x.fieldId === field?.id &&
                x.item.id === suggestion.itemId &&
                x.item.language === suggestion.mainItemLanguage &&
                x.item.version === suggestion.mainItemVersion);
            // Calculate the actual differences between oldValue and newValue
            const oldValue = suggestion.oldValue || field?.rawValue || "";
            const newValue = modifiedField?.value || suggestion.newValue || "";
            // If values are identical, no need to highlight
            if (oldValue === newValue) {
                setRanges([]);
                return;
            }
            // Save oldValue for use in the component
            setTextContent(oldValue);
            // Use the diff library to find the changed parts - using diffChars for more precise matching
            const diffResult = DiffLib.diffChars(oldValue, newValue);
            const changeRanges = [];
            let positionInOld = 0;
            // Process the diff result to find all change ranges
            for (const part of diffResult) {
                if (part.removed) {
                    // If a part is removed, highlight it in the original text
                    changeRanges.push([positionInOld, positionInOld + part.value.length]);
                    positionInOld += part.value.length;
                }
                else if (part.added) {
                    // For added parts, mark the insertion point
                    if (changeRanges.length > 0) {
                        const lastRange = changeRanges[changeRanges.length - 1];
                        if (lastRange && lastRange[1] === positionInOld) {
                            // If this addition comes right after a removal, the previous range already covers the position
                            continue;
                        }
                    }
                    // Mark the insertion point for new content
                    changeRanges.push([positionInOld, positionInOld]);
                }
                else {
                    // Unchanged part, just advance the position
                    positionInOld += part.value.length;
                }
            }
            // Merge adjacent or overlapping ranges
            const mergedRanges = [];
            if (changeRanges.length > 0) {
                changeRanges.sort((a, b) => a[0] - b[0]);
                // Start with the first range
                const firstRange = changeRanges[0];
                if (!firstRange) {
                    setRanges([]);
                    return;
                }
                let currentRange = [...firstRange];
                // Process the rest of the ranges
                for (let i = 1; i < changeRanges.length; i++) {
                    const nextRange = changeRanges[i];
                    // Skip undefined ranges
                    if (!nextRange)
                        continue;
                    // If the next range starts very close to the end of the current range, merge them
                    if (nextRange[0] <= currentRange[1] + 3) {
                        // Allow slightly larger gaps (3 chars) for better highlighting
                        // Extend the current range
                        currentRange[1] = Math.max(currentRange[1], nextRange[1]);
                    }
                    else {
                        // Save the current range and start a new one
                        mergedRanges.push([currentRange[0], currentRange[1]]);
                        currentRange = [...nextRange];
                    }
                }
                // Add the last range
                mergedRanges.push([currentRange[0], currentRange[1]]);
            }
            // Set ranges to highlight the changed parts
            setRanges(mergedRanges);
        };
        findRanges();
    }, [editContext, modifiedFields?.modifiedFields, suggestion]);
    if (!editContext)
        return null;
    if (!suggestion.fieldId)
        return null;
    const fieldElement = findFieldElement(iframe, {
        item: {
            id: suggestion.itemId,
            language: suggestion.mainItemLanguage,
            version: suggestion.mainItemVersion,
        },
        fieldId: suggestion.fieldId,
    });
    if (!fieldElement)
        return null;
    // Process each range and collect all DOM rectangles
    const allRects = [];
    ranges.forEach((range) => {
        if (range[0] !== undefined && range[1] !== undefined) {
            // For insertion points (zero-length ranges), expand to include at least one character
            // to make it visible
            const start = range[0];
            const end = range[0] === range[1]
                ? Math.min(range[0] + 1, textContent.length)
                : range[1];
            if (start < end) {
                const startData = getTextNodeAtPosition(fieldElement, start);
                const endData = getTextNodeAtPosition(fieldElement, end);
                if (startData && endData) {
                    try {
                        const textRange = document.createRange();
                        textRange.setStart(startData.node, startData.offset);
                        textRange.setEnd(endData.node, endData.offset);
                        const rangeRects = textRange.getClientRects();
                        // Convert DOMRectList to Array
                        for (let i = 0; i < rangeRects.length; i++) {
                            const rect = rangeRects[i];
                            if (rect) {
                                allRects.push(rect);
                            }
                        }
                    }
                    catch (e) {
                        console.error("Error creating range", e);
                    }
                }
            }
        }
    });
    // If no rects were found, fall back to highlighting the entire field
    if (allRects.length === 0 && ranges.length > 0) {
        const fallbackRect = fieldElement.getBoundingClientRect();
        allRects.push(fallbackRect);
    }
    const iframeRect = iframe.getBoundingClientRect();
    return (_jsx(_Fragment, { children: allRects.map((rect, index) => (_jsx("div", { style: {
                position: "absolute",
                top: rect.top * scale,
                left: rect.left * scale,
                width: rect.width * scale,
                height: rect.height * scale,
                pointerEvents: "none",
            }, children: index === 0 && (_jsx(SuggestionDisplayPopover, { suggestion: suggestion, onSuggestionUpdated: () => {
                    // The suggestion updates will be reflected when the component re-renders
                    // with the updated suggestedEdits from the edit context
                }, children: _jsx("div", { className: cn("pointer-events-auto absolute h-5 w-5 cursor-pointer", rect.top * scale < 18 ? "top-0" : "top-[-18px]", (rect.left + rect.width) * scale > iframeRect.width - 18
                        ? "right-0"
                        : "right-[-18px]"), onClick: () => {
                        editContext.select([suggestion.itemId]);
                        const fieldsContext = modifiedFields; // alias to use setter
                        fieldsContext?.setFocusedField({
                            item: {
                                id: suggestion.itemId,
                                language: suggestion.mainItemLanguage,
                                version: suggestion.mainItemVersion,
                            },
                            fieldId: suggestion.fieldId,
                        }, true);
                    }, children: _jsx(Lightbulb, { className: "rounded-full bg-emerald-400/70 p-1.5 text-white", size: 24, strokeWidth: 3 }) }) })) }, index))) }));
}
// Improved function to find text node at a given character position
function getTextNodeAtPosition(root, targetOffset) {
    // Handle edge cases
    if (targetOffset < 0)
        return null;
    // Get all text nodes in order
    const textNodes = [];
    const walker = document.createTreeWalker(root, NodeFilter.SHOW_TEXT, null);
    let node;
    while ((node = walker.nextNode())) {
        if (node)
            textNodes.push(node);
    }
    let currentOffset = 0;
    // Walk through all text nodes to find the one containing our target position
    for (const node of textNodes) {
        const nodeLength = node.textContent?.length || 0;
        // If this node contains our target position
        if (currentOffset + nodeLength >= targetOffset) {
            return {
                node: node,
                offset: targetOffset - currentOffset,
            };
        }
        currentOffset += nodeLength;
    }
    // If we've gone through all nodes and haven't found the position
    // Return the last position in the last text node
    if (textNodes.length > 0) {
        const lastNode = textNodes[textNodes.length - 1];
        if (lastNode) {
            return {
                node: lastNode,
                offset: lastNode.textContent?.length || 0,
            };
        }
    }
    return null;
}
//# sourceMappingURL=SuggestionHighlighting.js.map