import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
import { forwardRef, useEffect, useMemo, useRef, useState, } from "react";
import { useEditContext, useFieldsEditContext, } from "../client/editContext";
import { normalizeMarkerId } from "../utils";
import { CommentDisplayPopover } from "../reviews/CommentDisplayPopover";
import { measureBridgeComponentRect, measureBridgeFieldRect, measureBridgeTextRangeRects, rectListEquals, toOverlayRect, } from "./overlay/geometry";
import { useOverlayMeasurement } from "./overlay/useOverlayMeasurement";
import { getComponentById } from "../componentTreeHelper";
import { dedupeFeedbackHighlightItems, FeedbackHighlightBadge, FeedbackHighlightBadgeTray, } from "./FeedbackHighlightBadge";
import { COMMENT_DRAFT_SELECTION_EVENT, COMMENT_TRANSIENT_SELECTION_RECT, } from "../reviews/commentTransientSelection";
import { getCommentScrollIntoViewTarget, getCommentTextRange, getCommentTextRangeKey, getCommentTransientElementKey, } from "../reviews/feedbackScrollTarget";
const COMMENT_DRAFT_TEXT_RANGE_KEY = "comment-draft-selection";
const COMMENT_BRIDGE_SOURCE = "comments";
function commentMeasurementsEqual(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 CommentHighlightings({ pageViewContext, variant = "default", }) {
    const editContext = useEditContext();
    const modifiedFields = useFieldsEditContext();
    const lastMeasurementsRef = useRef({});
    const bridgeTextRangeRequestRef = useRef({
        signature: "",
        requestedSignature: "",
        hasSeenAnyTarget: false,
    });
    const [commentDraftRange, setCommentDraftRange] = useState();
    useEffect(() => {
        const handleDraftSelection = (event) => {
            const detail = event
                .detail;
            setCommentDraftRange(detail?.range);
        };
        window.addEventListener(COMMENT_DRAFT_SELECTION_EVENT, handleDraftSelection);
        return () => {
            window.removeEventListener(COMMENT_DRAFT_SELECTION_EVENT, handleDraftSelection);
        };
    }, []);
    const visibleComments = useMemo(() => (editContext?.comments || []).filter((comment) => !comment.suggestedEditId &&
        (editContext?.showResolvedComments ? true : !comment.isResolved)), [editContext?.comments, editContext?.showResolvedComments]);
    const bridgeTextRanges = useMemo(() => {
        const commentRanges = visibleComments.flatMap((comment) => {
            const range = getCommentTextRange(comment);
            if (!comment.fieldId || !range)
                return [];
            return getCommentFieldDescriptors(comment).map((descriptor) => ({
                key: getCommentTextRangeKey(comment),
                elementKey: getCommentTransientElementKey(comment),
                fieldId: descriptor.fieldId,
                item: descriptor.item,
                start: range[0],
                end: range[1],
            }));
        });
        const draftRange = getDraftBridgeTextRange(commentDraftRange);
        return draftRange ? [...commentRanges, draftRange] : commentRanges;
    }, [visibleComments, commentDraftRange]);
    const bridgeTextRangeSignature = useMemo(() => bridgeTextRanges
        .map((range) => [
        range.key,
        range.elementKey,
        range.fieldId,
        range.item.id,
        range.item.language,
        range.item.version,
        range.start,
        range.end,
    ].join(":"))
        .join("|"), [bridgeTextRanges]);
    useEffect(() => {
        const requestState = bridgeTextRangeRequestRef.current;
        if (!pageViewContext.bridgeReady || bridgeTextRanges.length === 0) {
            if (pageViewContext.bridgeReady && requestState.signature !== "") {
                // Drop our contribution from the shared union so the highlights clear
                // instead of lingering as phantom geometry targets.
                pageViewContext.requestBridgeGeometry?.({
                    source: COMMENT_BRIDGE_SOURCE,
                    textRanges: [],
                });
            }
            requestState.signature = "";
            requestState.requestedSignature = "";
            requestState.hasSeenAnyTarget = false;
            return;
        }
        const signatureChanged = requestState.signature !== bridgeTextRangeSignature;
        if (signatureChanged) {
            requestState.signature = bridgeTextRangeSignature;
            requestState.requestedSignature = "";
            requestState.hasSeenAnyTarget = false;
        }
        const bridgeGeometry = pageViewContext.bridgeGeometry;
        const hasAnyTarget = hasAnyExpectedBridgeTextRange(bridgeGeometry, bridgeTextRanges);
        const hasAllTargets = hasAllExpectedBridgeTextRanges(bridgeGeometry, bridgeTextRanges);
        if (hasAnyTarget) {
            requestState.hasSeenAnyTarget = true;
        }
        if (hasAllTargets && !signatureChanged) {
            requestState.requestedSignature = "";
            return;
        }
        const shouldRequest = signatureChanged ||
            requestState.requestedSignature !== bridgeTextRangeSignature ||
            (requestState.hasSeenAnyTarget && !hasAnyTarget);
        if (!shouldRequest)
            return;
        requestState.requestedSignature = bridgeTextRangeSignature;
        pageViewContext.requestBridgeGeometry?.({
            source: COMMENT_BRIDGE_SOURCE,
            textRanges: bridgeTextRanges,
        });
    }, [
        pageViewContext,
        pageViewContext.bridgeReady,
        pageViewContext.bridgeGeometry,
        bridgeTextRanges,
        bridgeTextRangeSignature,
        commentDraftRange,
    ]);
    const measurements = useOverlayMeasurement((metrics, reasons) => {
        if (!editContext)
            return {};
        const bridgeGeometry = pageViewContext.bridgeGeometry;
        const canReusePreviousMeasurement = !reasons.has("viewport") &&
            !reasons.has("layout") &&
            !reasons.has("text");
        const nextMeasurements = {};
        for (const comment of visibleComments) {
            const textRange = getCommentTextRange(comment);
            const fieldDescriptors = getCommentFieldDescriptors(comment);
            const hasTextRange = !!comment.fieldId && !!textRange;
            const textRangeRects = hasTextRange
                ? dedupeOverlayRects(measureBridgeTextRangeRects(bridgeGeometry, getCommentTextRangeKey(comment), metrics))
                : [];
            const previousMeasurement = lastMeasurementsRef.current[comment.id];
            const previousTextRangeRects = canReusePreviousMeasurement && previousMeasurement?.isTextRange
                ? previousMeasurement.rects
                : [];
            let rects = [];
            let isTextRange = false;
            if (hasTextRange) {
                rects = textRangeRects.length
                    ? textRangeRects
                    : previousTextRangeRects.length
                        ? previousTextRangeRects
                        : getTransientSelectionRects(comment, metrics, pageViewContext.editorIframe);
                isTextRange = rects.length > 0;
            }
            else {
                const fallbackRect = fieldDescriptors
                    .map((descriptor) => measureBridgeFieldRect(bridgeGeometry, pageViewContext.bridgeStructure, descriptor, metrics))
                    .find((rect) => !!rect) ??
                    measureBridgeCommentComponentRect(comment, pageViewContext, bridgeGeometry, metrics);
                rects = fallbackRect
                    ? [fallbackRect]
                    : canReusePreviousMeasurement && previousMeasurement?.rects.length
                        ? previousMeasurement.rects
                        : [];
                isTextRange =
                    !fallbackRect &&
                        canReusePreviousMeasurement &&
                        previousMeasurement?.isTextRange === true;
            }
            if (rects.length > 0) {
                nextMeasurements[comment.id] = {
                    rects,
                    isTextRange,
                };
            }
        }
        return nextMeasurements;
    }, [
        editContext,
        pageViewContext.bridgeGeometry,
        pageViewContext.bridgeStructure,
        visibleComments,
    ], {
        initialValue: {},
        equals: commentMeasurementsEqual,
        interests: ["viewport", "layout", "text", "adornment-data"],
    });
    const draftRects = useOverlayMeasurement((metrics) => {
        if (!commentDraftRange)
            return [];
        const bridgeRects = dedupeOverlayRects(measureBridgeTextRangeRects(pageViewContext.bridgeGeometry, COMMENT_DRAFT_TEXT_RANGE_KEY, metrics));
        const fallbackRects = getSelectionRangeClientRects(commentDraftRange, metrics, pageViewContext.editorIframe);
        const rects = bridgeRects.length ? bridgeRects : fallbackRects;
        return rects;
    }, [
        commentDraftRange,
        pageViewContext.bridgeGeometry,
        pageViewContext.editorIframe,
    ], {
        initialValue: [],
        equals: rectListEquals,
        interests: ["viewport", "layout", "text", "adornment-data"],
    });
    useEffect(() => {
        lastMeasurementsRef.current = measurements;
    }, [measurements]);
    const overlayGroups = useMemo(() => {
        const groups = new Map();
        for (const comment of visibleComments) {
            const measurement = measurements[comment.id];
            if (!measurement?.rects.length)
                continue;
            const key = getCommentMeasurementKey(measurement);
            const existing = groups.get(key);
            if (existing) {
                existing.comments.push(comment);
                continue;
            }
            groups.set(key, {
                key,
                comments: [comment],
                rects: measurement.rects,
                isTextRange: measurement.isTextRange,
            });
        }
        const nextGroups = Array.from(groups.values());
        return nextGroups;
    }, [visibleComments, measurements]);
    if (!editContext)
        return null;
    const selectedCommentId = editContext.selectedComment?.id;
    const selectedSuggestedEditId = editContext.selectedSuggestedEdit?.id;
    const hasSelectedFeedback = !!selectedCommentId || !!selectedSuggestedEditId;
    return (_jsxs(_Fragment, { children: [_jsx("style", { children: commentHighlightingStyles }), draftRects.map((rect, index) => (_jsx("div", { style: {
                    position: "absolute",
                    top: rect.y,
                    left: rect.x,
                    width: rect.width,
                    height: rect.height,
                    pointerEvents: "none",
                }, children: _jsx("div", { "data-testid": "comment-draft-highlight", className: "absolute right-0 bottom-[0.08em] left-0 h-[0.82em] min-h-[10px]", style: {
                        ...getDraftCommentRangeStyle(),
                        zIndex: 0,
                    } }) }, `comment-draft-${index}`))), overlayGroups.flatMap((group) => {
                const badgeComments = dedupeFeedbackHighlightItems(group.comments, (comment) => comment.id === editContext.selectedComment?.id);
                return group.rects.map((rect, index) => (_jsxs("div", { style: {
                        position: "absolute",
                        top: rect.y,
                        left: rect.x,
                        width: rect.width,
                        height: rect.height,
                        pointerEvents: "none",
                    }, children: [(hasSelectedFeedback
                            ? group.comments.map((comment) => [comment])
                            : [group.comments]).map((comments, highlightIndex) => {
                            const isSelected = comments.length === 1 && comments[0]?.id === selectedCommentId;
                            const isDimmed = hasSelectedFeedback && !isSelected;
                            const highlightKey = comments
                                .map((comment) => comment.id)
                                .join("-");
                            return group.isTextRange ? (_jsx("div", { "data-testid": "comment-highlight", "data-feedback-selected": isSelected ? "true" : undefined, "data-feedback-dimmed": isDimmed ? "true" : undefined, className: "absolute right-0 bottom-[0.08em] left-0 h-[0.82em] min-h-[10px]", style: {
                                    ...getCommentRangeStyle(comments, isSelected, isDimmed),
                                    zIndex: isSelected ? 2 : 0,
                                } }, highlightKey || highlightIndex)) : (_jsx("div", { "data-testid": "comment-highlight", "data-feedback-selected": isSelected ? "true" : undefined, "data-feedback-dimmed": isDimmed ? "true" : undefined, className: "absolute inset-0 rounded-[3px]", style: {
                                    ...getCommentFallbackStyle(comments, isSelected, isDimmed),
                                    zIndex: isSelected ? 2 : 0,
                                } }, highlightKey || highlightIndex));
                        }), index === 0 && (_jsx(FeedbackHighlightBadgeTray, { variant: variant, style: {
                                top: variant === "minimap"
                                    ? group.isTextRange
                                        ? -11
                                        : -8
                                    : group.isTextRange
                                        ? -24
                                        : -16,
                                left: variant === "minimap"
                                    ? group.isTextRange
                                        ? -4
                                        : 4
                                    : group.isTextRange
                                        ? -9
                                        : 8,
                            }, children: badgeComments.map((comment, badgeIndex) => {
                                const isSelected = comment.id === selectedCommentId;
                                const isDimmed = hasSelectedFeedback && !isSelected;
                                return (_jsx(CommentDisplayPopover, { comment: comment, onCommentUpdated: () => {
                                        editContext?.loadComments?.();
                                    }, children: _jsx(CommentHighlightBadge, { comment: comment, isSelected: isSelected, isDimmed: isDimmed, variant: variant, minimapColorClassName: "bg-highlight-100", zIndex: isSelected
                                            ? badgeComments.length + 101
                                            : badgeComments.length - badgeIndex + 100 }) }, comment.id));
                            }) }))] }, `${group.key}-${index}`)));
            })] }));
}
const CommentHighlightBadge = forwardRef(function CommentHighlightBadge({ comment, isSelected, isDimmed, variant, minimapColorClassName, zIndex, className, style, onClick, ...buttonProps }, ref) {
    const editContext = useEditContext();
    const modifiedFields = useFieldsEditContext();
    const handleClick = (event) => {
        onClick?.(event);
        if (event.defaultPrevented)
            return;
        editContext?.setSelectedComment(comment);
        editContext?.setSelectedSuggestedEdit(undefined);
        editContext?.setScrollIntoView(getCommentScrollIntoViewTarget(comment));
        editContext?.select([comment.itemId]);
        if (comment.fieldId) {
            modifiedFields?.setFocusedField({
                fieldId: comment.fieldId,
                item: {
                    id: comment.itemId,
                    language: comment.language,
                    version: comment.version,
                },
            }, false);
        }
    };
    return (_jsx(FeedbackHighlightBadge, { ref: ref, item: comment, isSelected: isSelected, isDimmed: isDimmed, variant: variant, minimapColorClassName: minimapColorClassName, zIndex: zIndex, testId: "comment-highlight-icon", ariaSubject: "comment", className: className, style: style, onClick: handleClick, ...buttonProps }));
});
function getCommentMeasurementKey(measurement) {
    const rectKey = measurement.rects
        .map((rect) => `${roundMeasurement(rect.x)}:${roundMeasurement(rect.y)}:${roundMeasurement(rect.width)}:${roundMeasurement(rect.height)}`)
        .join("|");
    return `${measurement.isTextRange ? "text" : "box"}:${rectKey}`;
}
function roundMeasurement(value) {
    return Math.round(value * 2) / 2;
}
function getDraftBridgeTextRange(range) {
    const textRange = getSelectionTextRange(range);
    if (!range?.fieldId || !range.itemId || !textRange)
        return undefined;
    return {
        key: COMMENT_DRAFT_TEXT_RANGE_KEY,
        elementKey: range.elementKey,
        fieldId: range.fieldId,
        item: {
            id: range.itemId,
            language: range.language || "",
            version: typeof range.version === "number" ? range.version : undefined,
        },
        start: textRange[0],
        end: textRange[1],
    };
}
function getSelectionTextRange(range) {
    if (!range?.text?.trim() ||
        typeof range.startOffset !== "number" ||
        typeof range.endOffset !== "number") {
        return null;
    }
    const start = Math.max(0, Math.min(range.startOffset, range.endOffset));
    const end = Math.max(start, range.startOffset, range.endOffset);
    return end > start ? [start, end] : null;
}
function hasAnyExpectedBridgeTextRange(geometry, textRanges) {
    if (!geometry)
        return false;
    const expectedKeys = new Set(textRanges.map((range) => range.key));
    return geometry.targets.some((target) => target.kind === "textRange" && expectedKeys.has(target.key));
}
function hasAllExpectedBridgeTextRanges(geometry, textRanges) {
    if (!geometry)
        return false;
    const availableKeys = new Set(geometry.targets
        .filter((target) => target.kind === "textRange")
        .map((target) => target.key));
    return textRanges.every((range) => availableKeys.has(range.key));
}
function getCommentFieldDescriptors(comment) {
    if (!comment.fieldId)
        return [];
    const items = getCommentCandidateItems(comment);
    return items.map((item) => ({
        fieldId: comment.fieldId,
        item,
    }));
}
function getCommentCandidateItems(comment) {
    const candidates = [];
    const addCandidate = (id) => {
        if (!id)
            return;
        const normalizedId = normalizeMarkerId(id);
        if (candidates.some((candidate) => normalizeMarkerId(candidate.id) === normalizedId)) {
            return;
        }
        candidates.push({
            id,
            language: comment.language,
            version: comment.version,
        });
    };
    addCandidate(comment.itemId);
    addCandidate(comment.mainItemId);
    comment.relatedItems?.forEach((item) => addCandidate(item.itemId));
    return candidates;
}
function measureBridgeCommentComponentRect(comment, pageViewContext, bridgeGeometry, metrics) {
    const page = pageViewContext?.page;
    if (!page)
        return null;
    for (const item of getCommentCandidateItems(comment)) {
        const component = getComponentById(item.id, page);
        if (!component)
            continue;
        const measurement = measureBridgeComponentRect(bridgeGeometry, component, metrics);
        if (measurement?.rect)
            return measurement.rect;
    }
    return null;
}
function getTransientSelectionRects(comment, metrics, iframe) {
    const rect = comment[COMMENT_TRANSIENT_SELECTION_RECT];
    return rect ? viewportSelectionRectToOverlayRects(rect, metrics, iframe) : [];
}
function getSelectionRangeClientRects(range, metrics, iframe) {
    const rect = range.clientRect;
    return rect ? viewportSelectionRectToOverlayRects(rect, metrics, iframe) : [];
}
function viewportSelectionRectToOverlayRects(rect, metrics, iframe) {
    if (!rect || rect.width <= 0 || rect.height <= 0)
        return [];
    const iframeRect = iframe?.getBoundingClientRect();
    return [
        toOverlayRect({
            left: iframeRect ? rect.left - iframeRect.left : rect.left,
            top: iframeRect ? rect.top - iframeRect.top : rect.top,
            width: rect.width,
            height: rect.height,
        }, metrics),
    ];
}
function dedupeOverlayRects(rects) {
    const seen = new Set();
    return rects.filter((rect) => {
        const key = [
            Math.round(rect.x * 100),
            Math.round(rect.y * 100),
            Math.round(rect.width * 100),
            Math.round(rect.height * 100),
        ].join(":");
        if (seen.has(key))
            return false;
        seen.add(key);
        return true;
    });
}
function getCommentRangeStyle(comments, isSelected, isDimmed = false) {
    const allResolved = comments.every((comment) => comment.isResolved);
    const color = allResolved
        ? "var(--color-feedback-green)"
        : "var(--color-highlight-100)";
    const fillStrength = isDimmed ? 10 : allResolved ? 18 : isSelected ? 42 : 28;
    const shadowStrength = isDimmed ? 0 : allResolved ? 8 : isSelected ? 28 : 12;
    return {
        background: `color-mix(in srgb, ${color} ${fillStrength}%, transparent)`,
        borderRadius: "2px",
        boxShadow: `0 1px 4px color-mix(in srgb, ${color} ${shadowStrength}%, transparent)`,
        opacity: isDimmed ? 0.42 : allResolved ? 0.72 : 1,
        ...(isSelected
            ? {
                animation: "parhelia-comment-highlight-pulse 2.8s ease-in-out infinite",
                willChange: "box-shadow, opacity",
            }
            : {}),
    };
}
function getDraftCommentRangeStyle() {
    return {
        background: "color-mix(in srgb, var(--color-highlight-100) 32%, transparent)",
        borderRadius: "2px",
        boxShadow: "0 1px 4px color-mix(in srgb, var(--color-highlight-100) 18%, transparent)",
    };
}
function getCommentFallbackStyle(comments, isSelected, isDimmed = false) {
    const allResolved = comments.every((comment) => comment.isResolved);
    const color = allResolved
        ? "var(--color-feedback-green)"
        : "var(--color-highlight-100)";
    return {
        background: `color-mix(in srgb, ${color} ${isDimmed ? 4 : isSelected ? 16 : 8}%, transparent)`,
        opacity: isDimmed ? 0.4 : allResolved ? 0.72 : 1,
    };
}
const commentHighlightingStyles = `
@keyframes parhelia-comment-highlight-pulse {
  0%, 100% {
    opacity: 0.9;
  }
  50% {
    opacity: 1;
  }
}
`;
//# sourceMappingURL=CommentHighlightings.js.map