import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
import { useEditContext, useFieldsEditContext } from "../client/editContext";
import { findFieldElement } from "../utils";
import { useState, useRef } from "react";
import { useEffect } from "react";
import { cn } from "../../lib/utils";
import { CommentIcon } from "../ui/Icons";
import { CommentDisplayPopover } from "../reviews/CommentDisplayPopover";
export function CommentHighlighting({ comment, scale, iframe, }) {
    const editContext = useEditContext();
    const isSelected = comment.id === editContext?.selectedComment?.id;
    const modifiedFields = useFieldsEditContext();
    const [range, setRange] = useState([
        comment.rangeStart || 0,
        comment.rangeEnd || 0,
    ]);
    const [rects, setRects] = useState(null);
    const [isTextRange, setIsTextRange] = useState(false);
    if (!editContext || !iframe)
        return null;
    useEffect(() => {
        const findRange = async () => {
            if (!editContext)
                return;
            if (comment.fieldValue &&
                comment.fieldId &&
                comment.rangeStart !== undefined &&
                comment.rangeEnd !== undefined) {
                const field = await editContext.itemsRepository.getField({
                    item: {
                        id: comment.itemId,
                        language: comment.language,
                        version: comment.version,
                    },
                    fieldId: comment.fieldId,
                });
                const modifiedField = modifiedFields?.modifiedFields.find((x) => x.fieldId === field?.id &&
                    x.item.id === comment.itemId &&
                    x.item.language === comment.language &&
                    x.item.version === comment.version);
                const mapping = computeMapping(modifiedField?.value || field?.rawValue || "", comment.fieldValue);
                const mappedRange = [
                    mapping[comment.rangeStart],
                    mapping[comment.rangeEnd],
                ];
                if (mappedRange[0] !== undefined && mappedRange[1] !== undefined) {
                    setRange(mappedRange);
                }
            }
        };
        findRange();
    }, [comment.fieldValue, modifiedFields?.modifiedFields]);
    const rafRef = useRef(null);
    useEffect(() => {
        if (!iframe) {
            setRects(null);
            setIsTextRange(false);
            return;
        }
        const calculateRects = () => {
            if (!iframe) {
                setRects(null);
                setIsTextRange(false);
                return;
            }
            const fieldElement = comment.fieldId
                ? findFieldElement(iframe, {
                    item: {
                        id: comment.itemId,
                        language: comment.language,
                        version: comment.version,
                    },
                    fieldId: comment.fieldId,
                })
                : undefined;
            let calculatedRects = null;
            let calculatedIsTextRange = false;
            if (fieldElement && range[0] !== undefined && range[1] !== undefined) {
                const startData = getTextNodeAtOffset(fieldElement, range[0]);
                const endData = getTextNodeAtOffset(fieldElement, range[1]);
                if (startData && endData) {
                    const textRange = document.createRange();
                    textRange.setStart(startData.node, startData.offset);
                    textRange.setEnd(endData.node, endData.offset);
                    calculatedRects = textRange.getClientRects();
                    calculatedIsTextRange = true;
                }
            }
            if (!calculatedRects || calculatedRects.length === 0) {
                let fallbackElement = fieldElement;
                if (!fallbackElement &&
                    comment.itemId &&
                    editContext.page &&
                    iframe.contentWindow?.document.body) {
                    const componentStart = iframe.contentWindow?.document.body.querySelector("[data-component-start='" + comment.itemId + "']");
                    fallbackElement = componentStart?.nextElementSibling;
                    while (fallbackElement && fallbackElement.tagName === "SCRIPT") {
                        fallbackElement = fallbackElement.nextElementSibling;
                    }
                }
                if (fallbackElement) {
                    const fallbackRect = fallbackElement.getBoundingClientRect();
                    if (fallbackRect) {
                        calculatedRects = {
                            length: 1,
                            0: fallbackRect,
                            item: (index) => (index === 0 ? fallbackRect : null),
                            [Symbol.iterator]: function* () {
                                yield fallbackRect;
                            },
                        };
                    }
                }
            }
            setRects(calculatedRects);
            setIsTextRange(calculatedIsTextRange);
        };
        const scheduleUpdate = () => {
            if (rafRef.current != null)
                window.cancelAnimationFrame(rafRef.current);
            rafRef.current = window.requestAnimationFrame(() => {
                rafRef.current = null;
                try {
                    calculateRects();
                }
                catch {
                    setRects(null);
                    setIsTextRange(false);
                }
            });
        };
        // Initial calculation
        calculateRects();
        // Get scroll container from iframe
        const iframeDoc = iframe.contentWindow?.document;
        const scrollContainer = iframeDoc?.scrollingElement || iframeDoc?.body || null;
        const iframeWindow = iframe.contentWindow;
        // Add scroll and resize listeners
        scrollContainer?.addEventListener("scroll", scheduleUpdate, {
            passive: true,
        });
        iframeWindow?.addEventListener("scroll", scheduleUpdate, { passive: true });
        iframeWindow?.addEventListener("resize", scheduleUpdate);
        // Cleanup function
        return () => {
            if (rafRef.current != null) {
                window.cancelAnimationFrame(rafRef.current);
                rafRef.current = null;
            }
            scrollContainer?.removeEventListener("scroll", scheduleUpdate);
            iframeWindow?.removeEventListener("scroll", scheduleUpdate);
            iframeWindow?.removeEventListener("resize", scheduleUpdate);
        };
    }, [
        comment.fieldId,
        comment.itemId,
        comment.language,
        comment.version,
        range,
        iframe,
        editContext,
    ]);
    if (!rects || rects.length === 0) {
        return null;
    }
    const iframeRect = iframe.getBoundingClientRect();
    return (_jsx(_Fragment, { children: Array.from(rects).map((rect, index) => (_jsxs("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(CommentDisplayPopover, { comment: comment, onCommentUpdated: () => {
                        // Reload comments to reflect any changes
                        editContext?.loadComments?.();
                    }, 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: () => {
                            // Set this comment as selected
                            editContext?.setSelectedComment(comment);
                            editContext?.setScrollIntoView(comment.itemId);
                            editContext?.select([comment.itemId]);
                            if (comment.fieldId) {
                                const fieldsContext = modifiedFields; // alias for readability
                                fieldsContext?.setFocusedField({
                                    fieldId: comment.fieldId,
                                    item: {
                                        id: comment.itemId,
                                        language: comment.language,
                                        version: comment.version,
                                    },
                                }, false);
                            }
                        }, children: _jsx(CommentIcon, { className: comment.isResolved
                                ? "text-green-500 hover:text-green-600"
                                : isSelected
                                    ? "text-red-700 hover:text-red-800"
                                    : "text-blue-500 hover:text-blue-600" }) }) })), isTextRange && (_jsx("div", { className: "opacity-45", style: {
                        width: "100%",
                        height: "100%",
                        backgroundColor: isSelected
                            ? "rgba(255, 0, 0, 1)" // Darker red for selected
                            : comment.isResolved
                                ? "rgba(0, 255, 0, 0.45)" // Green for resolved
                                : "rgba(0, 0, 255, 0.45)", // Red for unresolved
                    } }))] }, index))) }));
}
function getTextNodeAtOffset(root, offset) {
    const walker = document.createTreeWalker(root, NodeFilter.SHOW_TEXT, null);
    let currentNode;
    while ((currentNode = walker.nextNode())) {
        const nodeLength = currentNode.textContent?.length || 0;
        if (offset <= nodeLength) {
            return { node: currentNode, offset };
        }
        offset -= nodeLength;
    }
    return null;
}
/**
 * Erzeugt ein Mapping von Positionen im modifizierten String s2 auf die entsprechenden Positionen in s1,
 * basierend auf einer minimalen Edit-Sequenz (Levenshtein-Distance).
 *
 * @param s1 Originalstring
 * @param s2 Modifizierter String
 * @returns Ein Array, in dem mapping[j] die Position in s1 angibt, die erreicht wird, nachdem das j-te Zeichen in s2 erzeugt wurde.
 */
function computeMapping(s1, s2) {
    const n = s1.length;
    const m = s2.length;
    // 1. Aufbau der DP-Matrix und Backpointer-Matrix
    // dp[i][j] enthält die minimale Anzahl von Operationen, um s1[0..i-1] in s2[0..j-1] zu überführen.
    const dp = Array.from({ length: n + 1 }, () => new Array(m + 1).fill(0));
    const back = Array.from({ length: n + 1 }, () => new Array(m + 1).fill(""));
    // Initialisierung: leere Präfixe
    for (let i = 0; i <= n; i++) {
        dp[i][0] = i;
        if (i > 0) {
            back[i][0] = "D"; // "Deletion": Zeichen aus s1 wird gelöscht.
        }
    }
    for (let j = 0; j <= m; j++) {
        dp[0][j] = j;
        if (j > 0) {
            back[0][j] = "I"; // "Insertion": Zeichen in s2 wurde eingefügt.
        }
    }
    // Füllen der DP-Matrix (Wagner–Fischer-Algorithmus)
    for (let i = 1; i <= n; i++) {
        for (let j = 1; j <= m; j++) {
            const cost = s1[i - 1] === s2[j - 1] ? 0 : 1;
            // Drei Möglichkeiten:
            const deletion = dp[i - 1][j] + 1;
            const insertion = dp[i][j - 1] + 1;
            const substitution = dp[i - 1][j - 1] + cost; // "M" (Match) wenn cost==0, sonst "S" (Substitution)
            // Feste Tie-Breaking Reihenfolge: zuerst Deletion, dann Insertion, dann Substitution.
            let bestVal = deletion;
            let op = "D";
            if (insertion < bestVal) {
                bestVal = insertion;
                op = "I";
            }
            if (substitution < bestVal) {
                bestVal = substitution;
                op = cost === 0 ? "M" : "S";
            }
            dp[i][j] = bestVal;
            back[i][j] = op;
        }
    }
    // 2. Rekonstruktion des Edit-Skripts per Backtracking
    const ops = [];
    let i = n;
    let j = m;
    while (i > 0 || j > 0) {
        const op = back[i][j];
        ops.push(op);
        if (op === "M" || op === "S") {
            i--;
            j--;
        }
        else if (op === "D") {
            i--;
        }
        else if (op === "I") {
            j--;
        }
    }
    ops.reverse(); // Jetzt liegen die Operationen in der Vorwärtsreihenfolge vor.
    // 3. Simulation der Transformation von s1 zu s2 und Aufbau des Mappings.
    // mapping[j] soll die Position in s1 angeben, nachdem das j-te Zeichen in s2 erzeugt wurde.
    const mapping = new Array(m + 1);
    let iPos = 0; // Aktuelle Position in s1
    let jPos = 0; // Aktuelle Position in s2
    mapping[jPos] = iPos; // Vor jeglicher Transformation
    for (const op of ops) {
        if (op === "M" || op === "S") {
            // Bei Match oder Substitution wird in beiden Strings ein Zeichen verarbeitet.
            iPos++;
            jPos++;
            mapping[jPos] = iPos;
        }
        else if (op === "I") {
            // Bei Insertion wird in s2 ein Zeichen eingefügt, s1 bleibt unverändert.
            jPos++;
            mapping[jPos] = iPos;
        }
        else if (op === "D") {
            // Bei Deletion wird in s1 ein Zeichen übersprungen.
            iPos++;
        }
    }
    return mapping;
}
//# sourceMappingURL=CommentHighlighting.js.map