"use client";
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
import { useEffect, useRef, useState } from "react";
import { useEditContext } from "../client/editContext";
import { GripVertical } from "lucide-react";
import { cn } from "../../lib/utils";
import { getVisibleInsertOptions } from "../insertMenuItems";
import { InsertComponentMenu } from "./InsertComponentMenu";
import { Tooltip, TooltipContent, TooltipTrigger, } from "../../components/ui/tooltip";
import { componentFrameBgColorVariants, componentFrameBgHoverColorVariants, } from "../componentFrameColors";
import { buildInsertTargetLabel, formatPlaceholderDisplayName, } from "./insertTargetLabel";
export const SMALL_DROPZONE_SCALE = 0.44;
// Selected component chrome must paint above the generic canvas "+" controls
// (z 30/40). FrameMenu uses the same layer for its border/header, while its
// append pill is rendered as a sibling outside that stacking context.
export const FRAME_CHROME_Z_INDEX = 50;
// During a drag the "+" buttons sit ABOVE the insertion line (z 860, see
// InsertionLine.tsx). They remain interactive: the visible control is also the
// full-size hover/drop target instead of delegating its hit area to the surface
// underneath.
const DRAGGING_DROPZONE_Z_INDEX = 870;
const DROPZONE_POINTER_RECHECK_EVENT = "parhelia:placeholder-dropzone-pointer-recheck";
function DropZonePlus({ className }) {
    return (_jsx("svg", { "aria-hidden": "true", className: className, fill: "currentColor", focusable: "false", shapeRendering: "crispEdges", style: { pointerEvents: "none" }, viewBox: "0 0 24 24", children: _jsx("path", { d: "M11 5h2v6h6v2h-6v6h-2v-6H5v-2h6V5Z" }) }));
}
function getTopDropZoneHitTargetAtPoint(clientX, clientY) {
    if (typeof document === "undefined" || !document.elementsFromPoint) {
        return undefined;
    }
    for (const element of document.elementsFromPoint(clientX, clientY)) {
        const hitTarget = element.closest('[data-placeholder-dropzone-hit-target="true"]');
        if (hitTarget instanceof HTMLElement) {
            return hitTarget;
        }
    }
    return null;
}
function dispatchDropZonePointerRecheck(clientX, clientY) {
    if (typeof window === "undefined" ||
        !Number.isFinite(clientX) ||
        !Number.isFinite(clientY)) {
        return;
    }
    const topDropZone = getTopDropZoneHitTargetAtPoint(clientX, clientY);
    window.dispatchEvent(new CustomEvent(DROPZONE_POINTER_RECHECK_EVENT, {
        detail: { clientX, clientY, topDropZone },
    }));
}
function scheduleDropZonePointerRecheck(clientX, clientY) {
    if (typeof window === "undefined")
        return;
    if (typeof window.requestAnimationFrame === "function") {
        window.requestAnimationFrame(() => {
            dispatchDropZonePointerRecheck(clientX, clientY);
        });
        return;
    }
    window.setTimeout(() => {
        dispatchDropZonePointerRecheck(clientX, clientY);
    }, 0);
}
export function PlaceholderDropZone({ placeholder, description, index, parentName, spotPosition, displaySpotPosition, spotPositionElement, spotPositionAnchor, size, className, insertMenuTitle, typeIdOnCtrlClick, color = "default", frameColor, pillLabel, onDragHandlePointerDown, interactive = true, dragging = false, isActiveTarget = false, insertMenuKey, openInsertMenuKey, onInsertMenuOpenChange, }) {
    const [isHovering, setIsHovering] = useState(false);
    const [isPointerHovering, setIsPointerHovering] = useState(false);
    const [localMenuOpen, setLocalMenuOpen] = useState(false);
    const dropZoneRef = useRef(null);
    const editContext = useEditContext();
    const menuIsControlled = !!insertMenuKey && !!onInsertMenuOpenChange;
    const menuOpen = menuIsControlled
        ? openInsertMenuKey === insertMenuKey
        : localMenuOpen;
    const setMenuOpen = (open) => {
        if (menuIsControlled) {
            onInsertMenuOpenChange(insertMenuKey, open);
            return;
        }
        setLocalMenuOpen(open);
    };
    function dragOver(ev) {
        if (editContext?.mode !== "edit" || editContext?.readonly) {
            ev.dataTransfer.dropEffect = "none";
            return;
        }
        ev.dataTransfer.dropEffect =
            editContext?.dragObject?.type == "template" ? "copy" : "move";
        if (!isHovering)
            setIsHovering(true);
        ev.preventDefault();
    }
    function drop(ev) {
        if (editContext?.mode !== "edit" || editContext?.readonly) {
            ev.preventDefault();
            setIsHovering(false);
            return;
        }
        ev.preventDefault();
        setIsHovering(false);
        editContext.setStatusMessage("");
        const anchorElement = spotPositionElement ?? dropZoneRef.current;
        editContext.droppedInPlaceholder(placeholder.key, index, anchorElement ?? undefined, anchorElement ? spotPositionAnchor : undefined, undefined, spotPosition, color);
    }
    const visualSpotPosition = displaySpotPosition ?? spotPosition;
    const isVisuallyHovering = isHovering || isPointerHovering || menuOpen;
    const idleZIndex = pillLabel
        ? FRAME_CHROME_Z_INDEX
        : isVisuallyHovering
            ? 40
            : 30;
    const overlayStyle = {
        top: visualSpotPosition.y,
        left: visualSpotPosition.x,
        zIndex: dragging
            ? isActiveTarget
                ? DRAGGING_DROPZONE_Z_INDEX + 5
                : DRAGGING_DROPZONE_Z_INDEX
            : idleZIndex,
        transform: "none",
    };
    // Pills keep their full size; only the compact "+" dots scale down.
    if (size === "small" && !pillLabel) {
        overlayStyle.transform = `scale(${SMALL_DROPZONE_SCALE})`;
    }
    useEffect(() => {
        if (!editContext)
            return;
        if (isHovering) {
            const displayPlaceholderName = formatPlaceholderDisplayName(description || placeholder.name);
            editContext.setStatusMessage(_jsxs("div", { children: ["Drop into placeholder ", _jsx("span", { className: "font-bold", children: parentName }), " ", "/ ", _jsx("span", { className: "font-bold", children: displayPlaceholderName }), " at position ", _jsx("span", { className: "font-bold", children: index + 1 })] }));
        }
        else {
            editContext.setStatusMessage("");
        }
        return () => {
            // Clear status message when unmounting or when effect re-runs
            editContext.setStatusMessage("");
        };
        // Intentionally exclude editContext from deps to avoid effect loops when the
        // provider recreates the context value during updates.
        // eslint-disable-next-line react-hooks/exhaustive-deps
    }, [isHovering]);
    useEffect(() => {
        if (!interactive) {
            setIsPointerHovering(false);
            return;
        }
        const handlePointerRecheck = (event) => {
            const detail = event.detail;
            if (!detail ||
                !Number.isFinite(detail.clientX) ||
                !Number.isFinite(detail.clientY)) {
                return;
            }
            if (detail.topDropZone === undefined)
                return;
            setIsPointerHovering(detail.topDropZone === dropZoneRef.current);
        };
        window.addEventListener(DROPZONE_POINTER_RECHECK_EVENT, handlePointerRecheck);
        return () => {
            window.removeEventListener(DROPZONE_POINTER_RECHECK_EVENT, handlePointerRecheck);
        };
    }, [interactive]);
    // Return null after all hooks have been called to avoid breaking React's Rules of Hooks
    if (!editContext || editContext.readonly)
        return null;
    const visibleInsertOptions = getVisibleInsertOptions(placeholder.insertOptions);
    if (visibleInsertOptions.length === 0)
        return null;
    const visualColor = frameColor ?? color;
    const colorClassName = componentFrameBgColorVariants[visualColor];
    const hoverColorClassName = componentFrameBgHoverColorVariants[visualColor];
    const selectedInsertOption = visibleInsertOptions.find((x) => x.typeId === editContext.selectedForInsertion ||
        x.compatibleTypeIds?.includes(editContext.selectedForInsertion));
    // Resolve a friendly placeholder label for the tooltip title: implicit
    // placeholders are keyed by GUID, so swap the GUID for the datasource/item
    // display name when we can find a match.
    const formatPlaceholderLabel = (rawLabel, currentPlaceholder) => {
        const isImplicit = currentPlaceholder.key.startsWith("implicit_");
        if (!isImplicit)
            return rawLabel;
        const guidRegex = /([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12})/;
        const guidMatch = rawLabel.match(guidRegex);
        if (!guidMatch)
            return rawLabel;
        const guid = guidMatch[1].toLowerCase();
        const parentComponent = currentPlaceholder.parentComponent;
        const matchingItem = [
            parentComponent?.datasourceItem,
            ...(parentComponent?.items ?? []),
        ].find((item) => item?.id?.toLowerCase() === guid);
        const resolvedName = matchingItem?.displayName || matchingItem?.name;
        if (!resolvedName)
            return rawLabel;
        return rawLabel.replace(guidRegex, resolvedName);
    };
    // Name of the type that Ctrl+Click quick-inserts, shown in the tooltip.
    const ctrlTypeName = typeIdOnCtrlClick
        ? visibleInsertOptions.find((x) => x.typeId === typeIdOnCtrlClick)?.name
        : undefined;
    const placeholderDescription = formatPlaceholderDisplayName(formatPlaceholderLabel(description ?? placeholder.name, placeholder));
    const placeholderLabel = `${placeholderDescription} (${index + 1})`;
    const insertTargetLabel = buildInsertTargetLabel({
        placeholder,
        description: placeholderDescription,
        index,
    });
    // The pill's hover tooltip: placeholder context plus the Click / Ctrl+Click
    // interaction hint (the Ctrl+Click line only when a quick-insert type exists).
    const tooltipBody = typeIdOnCtrlClick ? (_jsxs("div", { className: "p-1 text-sm", children: [_jsx("div", { className: "mb-1.5 text-xs font-medium text-white", children: placeholderLabel }), _jsxs("div", { className: "space-y-0.5 text-xs text-white/85", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx("span", { className: "badge-pad-sm rounded border border-white/15 bg-white/15 font-mono text-[10px] text-white", children: "Click" }), _jsx("span", { children: "Open menu" })] }), _jsxs("div", { className: "flex items-center gap-2", children: [_jsx("span", { className: "badge-pad-sm rounded border border-white/15 bg-white/15 font-mono text-[10px] text-white", children: "Ctrl+Click" }), _jsxs("span", { children: ["Quick-add ", ctrlTypeName || "same type"] })] })] })] })) : (_jsx("div", { className: "p-1 text-sm", children: _jsx("div", { className: "text-xs font-medium text-white", children: placeholderLabel }) }));
    function dragLeave(ev) {
        // Check if we're actually leaving the drop zone (not just moving to a child element)
        const relatedTarget = ev.relatedTarget;
        if (dropZoneRef.current &&
            relatedTarget &&
            dropZoneRef.current.contains(relatedTarget)) {
            // Still within the drop zone, don't clear hovering state
            return;
        }
        setIsHovering(false);
    }
    function pointerEnter(ev) {
        setIsPointerHovering(true);
        dispatchDropZonePointerRecheck(ev.clientX, ev.clientY);
    }
    function pointerMove(ev) {
        dispatchDropZonePointerRecheck(ev.clientX, ev.clientY);
    }
    function pointerLeave(ev) {
        setIsPointerHovering(false);
        scheduleDropZonePointerRecheck(ev.clientX, ev.clientY);
    }
    const insertOptionAtThisGap = (option) => {
        void editContext.operations.addComponent(option.typeId, placeholder.key, index, editContext.currentItemDescriptor);
    };
    const handleDropZoneClick = (e) => {
        // Keep the click from bubbling to window-level close handlers.
        e.stopPropagation();
        // Quick-insert on Ctrl+Click: add a component of the provided type
        // immediately. preventDefault() stops the popover trigger from also opening.
        if (e.ctrlKey && typeIdOnCtrlClick) {
            e.preventDefault();
            const sameTypeOption = visibleInsertOptions.find((x) => x.typeId === typeIdOnCtrlClick);
            const anchorElement = spotPositionElement ?? dropZoneRef.current;
            if (sameTypeOption) {
                editContext.droppedInPlaceholder(placeholder.key, index, anchorElement ?? undefined, anchorElement ? spotPositionAnchor : undefined, sameTypeOption, spotPosition, color);
            }
            else {
                editContext.operations.addComponent(typeIdOnCtrlClick, placeholder.key, index, editContext.currentItemDescriptor);
            }
            return;
        }
        // A type pre-selected for insertion (insert mode) drops directly into this
        // gap - no menu. preventDefault() keeps the popover trigger from opening.
        const insertOption = visibleInsertOptions.find((x) => x.typeId === editContext.selectedForInsertion);
        if (insertOption) {
            e.preventDefault();
            const anchorElement = spotPositionElement ?? dropZoneRef.current;
            editContext.droppedInPlaceholder(placeholder.key, index, anchorElement ?? undefined, anchorElement ? spotPositionAnchor : undefined, insertOption, spotPosition, color);
            return;
        }
        // Otherwise let the InsertComponentMenu popover trigger toggle (do NOT
        // preventDefault): the menu opens anchored to this "+" button.
    };
    return (_jsx("div", { "data-testid": "placeholder-dropzone-overlay", className: cn("absolute", interactive ? "pointer-events-auto" : "pointer-events-none"), style: overlayStyle, children: pillLabel ? (_jsxs("div", { className: "pointer-events-none absolute top-1/2 left-1/2 flex -translate-x-1/2 -translate-y-1/2 items-center gap-1.5", children: [onDragHandlePointerDown && (_jsx("div", { onPointerDown: onDragHandlePointerDown, onClick: (e) => e.stopPropagation(), "aria-label": `Drag to move ${parentName ?? "component"}`, "data-testid": "placeholder-dropzone-drag-handle", className: cn("pointer-events-auto flex h-8 w-7 cursor-move items-center justify-center rounded-[4px] text-white transition-colors duration-100 ease-out select-none", colorClassName, hoverColorClassName, className), children: _jsx(GripVertical, { className: "h-5 w-5", strokeWidth: 1, style: { pointerEvents: "none" } }) })), _jsxs(Tooltip, { children: [_jsx(InsertComponentMenu, { open: menuOpen, onOpenChange: setMenuOpen, title: insertMenuTitle || "Insert Component", contextLabel: insertTargetLabel, options: visibleInsertOptions, onSelect: insertOptionAtThisGap, side: "right", align: "start", sideOffset: 12, children: _jsx(TooltipTrigger, { asChild: true, children: _jsx("div", { ref: dropZoneRef, onDragEnter: dragOver, onDragOver: dragOver, "data-testid": "placeholder-dropzone", "data-testindex": index, "data-parent-name": parentName || "", "data-placeholder-name": description || placeholder.name, "data-insert-option-type-ids": visibleInsertOptions
                                        .map((x) => x.typeId)
                                        .join("|"), "data-insert-option-names": visibleInsertOptions
                                        .map((x) => x.name)
                                        .join("|"), "data-selected-insert-type": selectedInsertOption
                                        ? editContext.selectedForInsertion
                                        : undefined, onDrop: drop, onDragLeave: dragLeave, onPointerEnter: pointerEnter, onPointerMove: pointerMove, onPointerLeave: pointerLeave, onClick: handleDropZoneClick, "aria-label": pillLabel, "data-placeholder-dropzone-hit-target": "true", className: cn("placeholder placeholder-dropzone group/add-component pointer-events-auto flex h-8 cursor-pointer items-center overflow-hidden rounded-[4px] px-2 text-[12px] font-normal whitespace-nowrap text-white transition-colors duration-100 ease-out select-none data-[state=open]:ring-2 data-[state=open]:ring-white/70", colorClassName, hoverColorClassName, className), children: _jsxs("span", { "data-testid": "placeholder-dropzone-button", className: "test-ph-dropzone tour-placeholder-dropzone flex items-center", children: [_jsx(DropZonePlus, { className: "h-4 w-4 shrink-0" }), _jsx("span", { className: "ml-0 max-w-0 translate-x-[-2px] overflow-hidden opacity-0 transition-[max-width,opacity,transform,margin] duration-200 ease-out group-hover/add-component:ml-1.5 group-hover/add-component:max-w-[7rem] group-hover/add-component:translate-x-0 group-hover/add-component:opacity-100 group-data-[state=open]/add-component:ml-1.5 group-data-[state=open]/add-component:max-w-[7rem] group-data-[state=open]/add-component:translate-x-0 group-data-[state=open]/add-component:opacity-100", children: pillLabel })] }) }) }) }), _jsx(TooltipContent, { side: "top", sideOffset: 8, className: "bg-neutral-grey-100/95 border-0 text-white shadow-xl backdrop-blur-md", children: tooltipBody })] })] })) : (_jsx(InsertComponentMenu, { open: menuOpen, onOpenChange: setMenuOpen, title: insertMenuTitle || "Insert Component", contextLabel: insertTargetLabel, options: visibleInsertOptions, onSelect: insertOptionAtThisGap, side: "right", align: "start", sideOffset: 24, children: _jsx("div", { ref: dropZoneRef, onDragEnter: dragOver, onDragOver: dragOver, "data-testid": "placeholder-dropzone", "data-testindex": index, "data-parent-name": parentName || "", "data-placeholder-name": description || placeholder.name, "data-insert-option-type-ids": visibleInsertOptions
                    .map((x) => x.typeId)
                    .join("|"), "data-insert-option-names": visibleInsertOptions
                    .map((x) => x.name)
                    .join("|"), "data-selected-insert-type": selectedInsertOption
                    ? editContext.selectedForInsertion
                    : undefined, onDrop: drop, onDragLeave: dragLeave, onPointerEnter: pointerEnter, onPointerMove: pointerMove, onPointerLeave: pointerLeave, onClick: handleDropZoneClick, "data-placeholder-dropzone-hit-target": "true", className: cn("placeholder placeholder-dropzone group/placeholder-dropzone relative flex h-8 w-8 -translate-x-1/2 -translate-y-1/2 cursor-pointer items-center justify-center", interactive ? "pointer-events-auto" : "pointer-events-none"), children: _jsxs("div", { draggable: false, className: cn("test-ph-dropzone tour-placeholder-dropzone", "pointer-events-none", "relative flex items-center justify-center", "h-8 w-8", "text-white"), "data-testid": "placeholder-dropzone-button", "data-active-target": isActiveTarget || undefined, children: [_jsx("span", { "aria-hidden": "true", "data-testid": "placeholder-dropzone-surface", className: cn("absolute inset-0 rounded-[4px] border border-white/50", "transition-[scale,box-shadow,filter] duration-300 ease-out", isActiveTarget
                                ? "scale-[1.25] shadow-md ring-2 ring-white"
                                : menuOpen
                                    ? "scale-[1.15] shadow-md ring-2 ring-white/80"
                                    : isVisuallyHovering
                                        ? "scale-[1.15] brightness-[0.88]"
                                        : "scale-100", colorClassName, className) }), _jsx(DropZonePlus, { className: "relative z-10 h-4 w-4" })] }) }) })) }));
}
//# sourceMappingURL=PlaceholderDropZone.js.map