import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
import { useEffect, useMemo, useState } from "react";
import { useEditContext, useFieldsEditContext } from "../client/editContext";
import { findFieldElement } from "../utils";
import { Lightbulb } from "lucide-react";
import * as DiffLib from "diff";
import { SuggestionDisplayPopover } from "../reviews/SuggestionDisplayPopover";
import { measureElementRect, measureTextRangeRects, rectListEquals, } from "./overlay/geometry";
import { getAccessibleIframeDocument } from "./overlay/iframeAccess";
import { useOverlayMeasurement } from "./overlay/useOverlayMeasurement";
function suggestionMeasurementsEqual(previous, next) {
    const previousKeys = Object.keys(previous);
    const nextKeys = Object.keys(next);
    if (previousKeys.length !== nextKeys.length)
        return false;
    for (const key of previousKeys) {
        const previousValue = previous[key];
        const nextValue = next[key];
        if (!previousValue || !nextValue)
            return false;
        if (previousValue.isTextRange !== nextValue.isTextRange)
            return false;
        if (!rectListEquals(previousValue.rects, nextValue.rects))
            return false;
    }
    return true;
}
export function SuggestionHighlightings({ iframe, }) {
    const editContext = useEditContext();
    const modifiedFields = useFieldsEditContext();
    const [rangeDataBySuggestionId, setRangeDataBySuggestionId] = useState({});
    const suggestions = useMemo(() => (editContext?.suggestedEdits || []).filter((suggestion) => suggestion.status !== "applied" && suggestion.status !== "rejected"), [editContext?.suggestedEdits]);
    useEffect(() => {
        if (!editContext) {
            setRangeDataBySuggestionId({});
            return;
        }
        let cancelled = false;
        const resolveRanges = async () => {
            const nextRangeData = {};
            await Promise.all(suggestions.map(async (suggestion) => {
                if (!suggestion.fieldId) {
                    nextRangeData[suggestion.id] = {
                        oldRanges: [],
                        newRanges: [],
                        oldText: "",
                        newText: "",
                    };
                    return;
                }
                const field = await editContext.itemsRepository.getField({
                    item: {
                        id: suggestion.itemId,
                        language: suggestion.mainItemLanguage,
                        version: suggestion.mainItemVersion,
                    },
                    fieldId: suggestion.fieldId,
                });
                const oldValue = toPlainSuggestionText(suggestion.oldValue ?? field?.rawValue ?? "");
                const newValue = toPlainSuggestionText(suggestion.newValue ?? "");
                if (oldValue === newValue) {
                    nextRangeData[suggestion.id] = {
                        oldRanges: [],
                        newRanges: [],
                        oldText: oldValue,
                        newText: newValue,
                    };
                    return;
                }
                const diffResult = DiffLib.diffChars(oldValue, newValue);
                const oldChangeRanges = [];
                const newChangeRanges = [];
                let positionInOld = 0;
                let positionInNew = 0;
                for (const part of diffResult) {
                    if (part.removed) {
                        oldChangeRanges.push([
                            positionInOld,
                            positionInOld + part.value.length,
                        ]);
                        positionInOld += part.value.length;
                    }
                    else if (part.added) {
                        newChangeRanges.push([
                            positionInNew,
                            positionInNew + part.value.length,
                        ]);
                        positionInNew += part.value.length;
                    }
                    else {
                        positionInOld += part.value.length;
                        positionInNew += part.value.length;
                    }
                }
                nextRangeData[suggestion.id] = {
                    oldRanges: mergeRanges(oldChangeRanges),
                    newRanges: mergeRanges(newChangeRanges),
                    oldText: oldValue,
                    newText: newValue,
                };
            }));
            if (!cancelled)
                setRangeDataBySuggestionId(nextRangeData);
        };
        resolveRanges();
        return () => {
            cancelled = true;
        };
    }, [editContext, suggestions]);
    const measurements = useOverlayMeasurement((metrics) => {
        const iframeDocument = getAccessibleIframeDocument(iframe);
        if (!iframeDocument?.body)
            return {};
        const nextMeasurements = {};
        for (const suggestion of suggestions) {
            if (!suggestion.fieldId)
                continue;
            const fieldElement = findFieldElement(iframe, {
                item: {
                    id: suggestion.itemId,
                    language: suggestion.mainItemLanguage,
                    version: suggestion.mainItemVersion,
                },
                fieldId: suggestion.fieldId,
            });
            if (!fieldElement)
                continue;
            const rangeData = rangeDataBySuggestionId[suggestion.id];
            if (!rangeData)
                continue;
            const ranges = getRangesForRenderedSuggestionText(rangeData, fieldElement.textContent || "");
            const rects = [];
            let isTextRange = false;
            for (const [start, end] of ranges) {
                rects.push(...measureTextRangeRects(iframeDocument, fieldElement, start, end, metrics));
            }
            isTextRange = rects.length > 0;
            if (rects.length === 0 && rangeData.oldText !== rangeData.newText) {
                const fallbackRect = measureElementRect(fieldElement, metrics);
                if (fallbackRect)
                    rects.push(fallbackRect);
            }
            if (rects.length > 0) {
                nextMeasurements[suggestion.id] = { rects, isTextRange };
            }
        }
        return nextMeasurements;
    }, [iframe, suggestions, rangeDataBySuggestionId], {
        initialValue: {},
        equals: suggestionMeasurementsEqual,
        interests: ["viewport", "layout", "text", "adornment-data"],
    });
    if (!editContext)
        return null;
    return (_jsx(_Fragment, { children: suggestions.flatMap((suggestion) => {
            const measurement = measurements[suggestion.id];
            if (!measurement?.rects.length)
                return [];
            return measurement.rects.map((rect, index) => (_jsxs("div", { style: {
                    position: "absolute",
                    top: rect.y,
                    left: rect.x,
                    width: rect.width,
                    height: rect.height,
                    pointerEvents: "none",
                }, children: [index === 0 && (_jsx(SuggestionDisplayPopover, { suggestion: suggestion, onSuggestionUpdated: () => { }, children: _jsx("div", { className: "pointer-events-auto absolute top-0 right-0 h-5 w-5 cursor-pointer", onClick: () => {
                                editContext.select([suggestion.itemId]);
                                modifiedFields?.setFocusedField({
                                    item: {
                                        id: suggestion.itemId,
                                        language: suggestion.mainItemLanguage,
                                        version: suggestion.mainItemVersion,
                                    },
                                    fieldId: suggestion.fieldId,
                                }, true);
                            }, children: _jsx(Lightbulb, { className: "bg-feedback-green/70 rounded-full p-1.5 text-white", size: 24, strokeWidth: 3 }) }) })), measurement.isTextRange && (_jsx("div", { "data-testid": "suggested-edit-highlight", className: "bg-feedback-green/15 rounded-sm", style: { width: "100%", height: "100%" } }))] }, `${suggestion.id}-${index}`)));
        }) }));
}
function toPlainSuggestionText(value) {
    if (!value || !/[<&]/.test(value))
        return value || "";
    const template = document.createElement("template");
    template.innerHTML = value;
    return template.content.textContent || "";
}
function getRangesForRenderedSuggestionText(rangeData, renderedText) {
    if (renderedText === rangeData.newText)
        return rangeData.newRanges;
    if (renderedText === rangeData.oldText)
        return rangeData.oldRanges;
    const newIndex = rangeData.newText
        ? renderedText.indexOf(rangeData.newText)
        : -1;
    if (newIndex >= 0) {
        return offsetRanges(rangeData.newRanges, newIndex);
    }
    const oldIndex = rangeData.oldText
        ? renderedText.indexOf(rangeData.oldText)
        : -1;
    if (oldIndex >= 0) {
        return offsetRanges(rangeData.oldRanges, oldIndex);
    }
    const preferNew = rangeData.newRanges.length > 0 ||
        renderedText.length >= rangeData.newText.length;
    return preferNew ? rangeData.newRanges : rangeData.oldRanges;
}
function offsetRanges(ranges, offset) {
    return ranges.map(([start, end]) => [start + offset, end + offset]);
}
function mergeRanges(changeRanges) {
    const mergedRanges = [];
    if (changeRanges.length === 0)
        return mergedRanges;
    changeRanges.sort((a, b) => a[0] - b[0]);
    let currentRange = [...changeRanges[0]];
    for (let i = 1; i < changeRanges.length; i++) {
        const nextRange = changeRanges[i];
        if (!nextRange)
            continue;
        if (nextRange[0] <= currentRange[1] + 3) {
            currentRange[1] = Math.max(currentRange[1], nextRange[1]);
        }
        else {
            mergedRanges.push([currentRange[0], currentRange[1]]);
            currentRange = [...nextRange];
        }
    }
    mergedRanges.push([currentRange[0], currentRange[1]]);
    return mergedRanges;
}
//# sourceMappingURL=SuggestionHighlightings.js.map