import { Fragment as _Fragment, jsx as _jsx } from "react/jsx-runtime";
import { useEffect, useRef, useState } from "react";
import { useEditContext } from "../client/editContext";
import { useSlotContext } from "../views/editorSlotContext";
import { getAllPlaceholders } from "../componentTreeHelper";
import { PlaceholderDropZone, SMALL_DROPZONE_SCALE, } from "./PlaceholderDropZone";
import { useOverlayMeasurement } from "./overlay/useOverlayMeasurement";
import { computePlaceholderZones, layoutAwareIndex, placeholderZonesEqual, } from "./overlay/placeholderZones";
import { getPlaceholderDropZoneColor } from "../componentFrameColors";
import { usePinnedHoveredComponentId } from "./usePinnedHoveredComponentId";
export function PlaceholderDropZones({ size, pageViewContext, scopeInsertModeToHoveredComponent = false, avoidOverlaps = true, activeTargetKey = null, openInsertMenuKey, onInsertMenuOpenChange, }) {
    const [placeholders, setPlaceholders] = useState([]);
    // Last good dot set captured during a drag, so a transient mid-drag
    // measurement frame that drops gaps can't unmount/remount the dots.
    const dragZonesSnapshotRef = useRef({ active: false, zones: [] });
    const editContext = useEditContext();
    const slotContext = useSlotContext();
    useEffect(() => {
        if (!pageViewContext?.page)
            return;
        setPlaceholders(getAllPlaceholders(pageViewContext.page));
    }, [pageViewContext?.page]);
    const hoveredComponentId = usePinnedHoveredComponentId(pageViewContext);
    const zones = useOverlayMeasurement((metrics) => {
        if (!slotContext?.isActive || !pageViewContext)
            return [];
        const filteredZones = computePlaceholderZones({
            placeholders,
            geometry: pageViewContext.bridgeGeometry,
            metrics,
            dragObject: editContext?.dragObject,
            selectedForInsertion: editContext?.selectedForInsertion,
            insertMode: editContext?.insertMode,
            showLayoutComponents: editContext?.showLayoutComponents,
            hoveredComponentId,
            scopeInsertModeToHoveredComponent,
        });
        // Dots get presentation-only overlap avoidance so multiple "+" buttons at
        // the same spot don't stack. Overview surfaces (minimap) opt out, and
        // during a drag the dots sit at TRUE gap positions so the active "+"
        // lines up exactly with the insertion line.
        const displayZones = avoidOverlaps && !editContext?.dragObject
            ? applyDropZoneOverlapAvoidance(filteredZones, metrics, getDropZoneOverlapOptions(size))
            : filteredZones;
        // Keep the dot set stable during a drag. A mid-drag measurement frame can
        // momentarily drop a gap (auto-scroll / reflow leaves a component rect
        // unmeasured), which would unmount and remount that dot - a hard pop,
        // since the dots have no fade. Retain the last good set on any frame that
        // drops gaps; accept frames that carry the full set so positions keep
        // tracking the page. The baseline resets when a new drag begins.
        const snap = dragZonesSnapshotRef.current;
        if (editContext?.dragObject) {
            if (snap.active && displayZones.length < snap.zones.length) {
                return snap.zones;
            }
            dragZonesSnapshotRef.current = { active: true, zones: displayZones };
            return displayZones;
        }
        if (snap.active) {
            dragZonesSnapshotRef.current = { active: false, zones: [] };
        }
        return displayZones;
    }, [
        slotContext?.isActive,
        pageViewContext,
        pageViewContext?.bridgeGeometry,
        editContext?.dragObject,
        editContext?.selectedForInsertion,
        editContext?.insertMode,
        editContext?.showLayoutComponents,
        hoveredComponentId,
        scopeInsertModeToHoveredComponent,
        avoidOverlaps,
        size,
        placeholders,
    ], {
        initialValue: [],
        equals: placeholderZonesEqual,
        interests: ["viewport", "layout", "adornment-data"],
    });
    // The "+" buttons show for no-drag insert mode / click-to-insert and remain
    // real hover/drop targets during a drag. The continuous insertion surface
    // still covers the rest of the canvas, while each visible 32x32 button owns
    // its complete hit area.
    if (!zones.length || !editContext)
        return null;
    const dragging = !!editContext.dragObject;
    return (_jsx(_Fragment, { children: zones.map((zone) => {
            const zoneKey = getPlaceholderZoneKey(zone);
            const components = zone.placeholder.components;
            const prevComponent = zone.index >= 1 ? components[zone.index - 1] : null;
            const nextComponent = zone.index < components.length ? components[zone.index] : null;
            const color = getPlaceholderDropZoneColor({
                parentComponent: zone.placeholder.parentComponent,
                prevComponent,
                nextComponent,
            });
            const adjustedIndex = layoutAwareIndex(zone.placeholder.components, zone.index);
            return (_jsx(PlaceholderDropZone, { placeholder: zone.placeholder, spotPosition: { x: zone.position.x, y: zone.position.y }, index: adjustedIndex, size: size, spotPositionElement: zone.element, spotPositionAnchor: zone.anchor, parentName: zone.placeholder.parentComponent?.name, description: zone.description || undefined, color: color, dragging: dragging, isActiveTarget: !!activeTargetKey && zoneKey === activeTargetKey, insertMenuKey: `placeholder:${zoneKey}`, openInsertMenuKey: openInsertMenuKey, onInsertMenuOpenChange: onInsertMenuOpenChange }, zoneKey));
        }) }));
}
function getPlaceholderZoneKey(zone) {
    return `${zone.placeholder.key}#${zone.index}`;
}
export function applyDropZoneOverlapAvoidance(zones, metrics, options = getDropZoneOverlapOptions("large")) {
    const scrollHeight = metrics.scrollHeight * metrics.visualScale;
    const scrollWidth = metrics.scrollWidth * metrics.visualScale;
    const { edgePadding, overlapThreshold, shiftStep } = options;
    const contentWidth = zones.reduce((max, zone) => Math.max(max, zone.position.x + edgePadding), scrollWidth);
    const contentHeight = zones.reduce((max, zone) => Math.max(max, zone.position.y + edgePadding), scrollHeight);
    const usedPositions = new Map();
    return zones.map((zone) => {
        let x = clampToDropZoneBounds(zone.position.x, contentWidth, edgePadding);
        let y = clampToDropZoneBounds(zone.position.y, contentHeight, edgePadding);
        const posKey = `${Math.round(x / overlapThreshold)}_${Math.round(y / overlapThreshold)}`;
        const overlapCount = usedPositions.get(posKey) || 0;
        if (overlapCount > 0) {
            x = x + overlapCount * shiftStep;
            if (x > contentWidth - edgePadding) {
                x = edgePadding + ((overlapCount - 1) % 5) * shiftStep;
                y = y + Math.floor((overlapCount - 1) / 5) * shiftStep;
            }
        }
        usedPositions.set(posKey, overlapCount + 1);
        return {
            ...zone,
            position: { x, y },
        };
    });
}
function getDropZoneOverlapOptions(size) {
    const visualSize = size === "small" ? 32 * SMALL_DROPZONE_SCALE : 32;
    return {
        edgePadding: Math.max(visualSize / 2 + 2, 6),
        overlapThreshold: visualSize,
        shiftStep: visualSize + (size === "small" ? 4 : 8),
    };
}
function clampToDropZoneBounds(value, contentSize, padding) {
    const min = padding;
    const max = contentSize - padding;
    if (max < min)
        return Math.max(0, contentSize / 2);
    return Math.min(max, Math.max(min, value));
}
//# sourceMappingURL=PlaceholderDropZones.js.map