import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
import { useState, useEffect, useCallback, useRef } from "react";
import { useCanvasSelection, useEditContext } from "../../client-components";
import { Logo } from "../ui/Icons";
import { Loader2 } from "lucide-react";
import { post } from "../services/serviceHelper";
import { CloseButton } from "../../components/ui/close-button";
export function GuidanceOverlay() {
    const [stepQueue, setStepQueue] = useState([]);
    const [currentStep, setCurrentStep] = useState(null);
    const [isWaitingForNextStep, setIsWaitingForNextStep] = useState(false);
    const [isFinalSequence, setIsFinalSequence] = useState(false);
    const [totalSteps, setTotalSteps] = useState(0);
    const lastProcessedStepRef = useRef(null);
    const [focusRects, setFocusRects] = useState([]); // Support multiple highlights
    const [pointToRect, setPointToRect] = useState(null);
    const [show, setShow] = useState(false);
    const editContext = useEditContext();
    const canvasSelection = useCanvasSelection();
    const bubbleRef = useRef(null);
    // Track active listeners (multiple for multi-element selection)
    const activeListenersRef = useRef([]);
    // Track active timeouts so they can be cleared on step change/unmount
    const activeTimeoutsRef = useRef([]);
    // Parse markdown-style bold formatting (**text**) and convert to React elements
    const parseBoldFormatting = useCallback((text) => {
        if (!text)
            return text;
        const parts = [];
        const regex = /\*\*(.+?)\*\*/g;
        let lastIndex = 0;
        let match;
        while ((match = regex.exec(text)) !== null) {
            // Add text before the bold section
            if (match.index > lastIndex) {
                parts.push(text.substring(lastIndex, match.index));
            }
            // Add the bold text
            parts.push(_jsx("strong", { children: match[1] }, match.index));
            lastIndex = regex.lastIndex;
        }
        // Add remaining text after the last match
        if (lastIndex < text.length) {
            parts.push(text.substring(lastIndex));
        }
        // If no matches found, return original text
        return parts.length > 0 ? parts : text;
    }, []);
    // Normalize selector to handle quote variations, smart quotes, and encoding issues
    const normalizeSelector = useCallback((selector) => {
        let normalized = selector;
        // Replace smart/curly quotes with straight quotes
        normalized = normalized.replace(/[\u2018\u2019\u201A\u201B]/g, "'"); // Single curly quotes
        normalized = normalized.replace(/[\u201C\u201D\u201E\u201F]/g, '"'); // Double curly quotes
        // Handle potential HTML entities
        normalized = normalized.replace(/&quot;/g, '"');
        normalized = normalized.replace(/&#34;/g, '"');
        normalized = normalized.replace(/&#39;/g, "'");
        normalized = normalized.replace(/&apos;/g, "'");
        return normalized;
    }, []);
    // Find ALL matching elements and return their rects + elements for event listeners
    // Waits for element positions to stabilize (no movement for several frames) before returning
    const waitForElements = useCallback(async (selector, timeout = 5000) => {
        const startTime = Date.now();
        const findElements = () => {
            // Normalize the selector first
            const normalizedSelector = normalizeSelector(selector);
            const selectors = normalizedSelector.split(",").map((s) => s.trim());
            const allRects = [];
            const allElements = [];
            for (const s of selectors) {
                if (s.startsWith("iframe:")) {
                    continue;
                }
                else {
                    let elements = document.querySelectorAll(s);
                    // Fallback: if selector starts with #, try finding by data-testid
                    if (elements.length === 0 && s.startsWith("#")) {
                        const id = s.substring(1);
                        elements = document.querySelectorAll(`[data-testid="${id}"]`);
                    }
                    // Fallback: try converting single quotes to double quotes
                    if (elements.length === 0 &&
                        s.includes("data-testid") &&
                        s.includes("'")) {
                        const doubleQuoteSelector = s.replace(/'/g, '"');
                        elements = document.querySelectorAll(doubleQuoteSelector);
                    }
                    // Fallback: try converting double quotes to single quotes (some browsers may handle differently)
                    if (elements.length === 0 &&
                        s.includes("data-testid") &&
                        s.includes('"')) {
                        const singleQuoteSelector = s.replace(/"/g, "'");
                        elements = document.querySelectorAll(singleQuoteSelector);
                    }
                    // Fallback: try extracting data-testid value and search directly
                    if (elements.length === 0 && s.includes("data-testid")) {
                        const match = s.match(/data-testid\s*=\s*["']([^"']+)["']/);
                        if (match && match[1]) {
                            const testId = match[1];
                            // Try both quote styles
                            elements = document.querySelectorAll(`[data-testid="${testId}"]`);
                            if (elements.length === 0) {
                                elements = document.querySelectorAll(`[data-testid='${testId}']`);
                            }
                        }
                    }
                    // Debug logging - only log once at the start
                    if (elements.length === 0 && Date.now() - startTime < 100) {
                        console.log(`[GuidanceOverlay] Searching for selector: "${s}" (original: "${selector}")`);
                    }
                    elements.forEach((element) => {
                        allRects.push(element.getBoundingClientRect());
                        allElements.push(element);
                    });
                }
            }
            return allRects.length > 0
                ? { rects: allRects, elements: allElements }
                : undefined;
        };
        // Helper to check if rects are approximately equal (within 1px tolerance)
        const rectsEqual = (a, b) => {
            if (a.length !== b.length)
                return false;
            const tolerance = 1;
            return a.every((rectA, i) => {
                const rectB = b[i];
                if (!rectB)
                    return false;
                return (Math.abs(rectA.left - rectB.left) < tolerance &&
                    Math.abs(rectA.top - rectB.top) < tolerance &&
                    Math.abs(rectA.width - rectB.width) < tolerance &&
                    Math.abs(rectA.height - rectB.height) < tolerance);
            });
        };
        return new Promise((resolve) => {
            let lastRects = null;
            let stableFrames = 0;
            const requiredStableFrames = 5; // Require 5 frames of stability (~83ms at 60fps)
            const minStabilizationTime = 100; // Minimum time to wait for CSS transitions
            let foundTime = null;
            const check = () => {
                const result = findElements();
                if (result) {
                    // Mark when we first found elements
                    if (foundTime === null) {
                        foundTime = Date.now();
                    }
                    // Check if positions have stabilized
                    if (lastRects && rectsEqual(result.rects, lastRects)) {
                        stableFrames++;
                    }
                    else {
                        stableFrames = 0;
                    }
                    lastRects = result.rects;
                    // Wait for positions to stabilize AND minimum time to pass
                    const elapsedSinceFound = Date.now() - foundTime;
                    if (stableFrames >= requiredStableFrames &&
                        elapsedSinceFound >= minStabilizationTime) {
                        const extendFocus = 10;
                        const expandedRects = result.rects.map((rect) => new DOMRect(rect.left - extendFocus / 2, rect.top - extendFocus / 2, rect.width + extendFocus, rect.height + extendFocus));
                        resolve({ rects: expandedRects, elements: result.elements });
                        return;
                    }
                }
                else {
                    // Reset if elements disappear
                    lastRects = null;
                    stableFrames = 0;
                    foundTime = null;
                }
                if (Date.now() - startTime >= timeout) {
                    // Timeout - return whatever we have
                    if (result) {
                        const extendFocus = 10;
                        const expandedRects = result.rects.map((rect) => new DOMRect(rect.left - extendFocus / 2, rect.top - extendFocus / 2, rect.width + extendFocus, rect.height + extendFocus));
                        resolve({ rects: expandedRects, elements: result.elements });
                    }
                    else {
                        resolve(undefined);
                    }
                }
                else {
                    requestAnimationFrame(check);
                }
            };
            check();
        });
    }, [normalizeSelector]);
    const reportActionCompleted = useCallback((step, isLastStep) => {
        // Only notify the agent when all queued steps are done
        if (!isLastStep || !step.agentId)
            return;
        if (!editContext) {
            console.error("[GuidanceOverlay] Cannot submit guidance action completion: editContext is undefined");
            return;
        }
        void (async () => {
            const result = await post("/parhelia/agent/submitGuidanceAction", {
                title: step.title,
                agentId: step.agentId,
            }, editContext.sessionId);
            if (result.type !== "success" || result.data?.success !== true) {
                console.error("[GuidanceOverlay] Failed to submit guidance action completion:", result.summary, result.details);
                editContext.showToast(result.summary || "Failed to continue guided action");
            }
        })();
    }, [editContext]);
    // Helper to clean up all active listeners
    const cleanupListeners = useCallback(() => {
        activeListenersRef.current.forEach(({ element, type, handler }) => {
            // Remove both capture and bubble listeners (we may register either)
            element.removeEventListener(type, handler, true);
            element.removeEventListener(type, handler, false);
        });
        activeListenersRef.current = [];
        activeTimeoutsRef.current.forEach((id) => window.clearTimeout(id));
        activeTimeoutsRef.current = [];
    }, []);
    const dismissGuidance = useCallback(() => {
        cleanupListeners();
        setCurrentStep(null);
        setStepQueue([]);
        setShow(false);
        setFocusRects([]);
        setIsWaitingForNextStep(false);
        setTotalSteps(0);
        lastProcessedStepRef.current = null;
        reportedStepRef.current = null;
    }, [cleanupListeners]);
    // Process next step from queue
    const processNextStep = useCallback(() => {
        setStepQueue((prevQueue) => {
            if (prevQueue.length === 0) {
                // Queue is empty, wait for agent
                setIsWaitingForNextStep(true);
                setFocusRects([]);
                return prevQueue;
            }
            const [nextStep, ...remaining] = prevQueue;
            setCurrentStep(nextStep ?? null);
            lastProcessedStepRef.current = null; // Allow processing new step
            setIsWaitingForNextStep(false);
            return remaining;
        });
    }, []);
    // Track if we've already reported for current step to prevent duplicates
    const reportedStepRef = useRef(null);
    // Track if we've already scheduled the socket message send (prevents React Strict Mode double-fire)
    const sentSocketMessageRef = useRef(null);
    // Track isFinalSequence in a ref so it can be accessed in callbacks
    const isFinalSequenceRef = useRef(false);
    // Keep ref in sync with state
    useEffect(() => {
        isFinalSequenceRef.current = isFinalSequence;
    }, [isFinalSequence]);
    // Handle action completion - either show next queued step or wait for agent
    const onActionCompleted = useCallback((step) => {
        // Prevent duplicate reports for the same step
        const stepKey = `${step.title}-${step.focusElement}`;
        if (reportedStepRef.current === stepKey) {
            return; // Already reported this step
        }
        reportedStepRef.current = stepKey;
        // Clean up all listeners
        cleanupListeners();
        // Capture isLastStep BEFORE state update to avoid React double-fire issues
        let capturedIsLastStep = false;
        // Check queue length synchronously before state update
        setStepQueue((prevQueue) => {
            capturedIsLastStep = prevQueue.length === 0;
            if (capturedIsLastStep) {
                // No more queued steps
                if (isFinalSequenceRef.current) {
                    // Final sequence - dismiss the guidance completely
                    setShow(false);
                    setCurrentStep(null);
                    setFocusRects([]);
                }
                else {
                    // More instructions expected - show loading state
                    setIsWaitingForNextStep(true);
                    setFocusRects([]);
                }
                return prevQueue;
            }
            else {
                // Process next step from queue
                const [nextStep, ...remaining] = prevQueue;
                setCurrentStep(nextStep ?? null);
                lastProcessedStepRef.current = null;
                // DON'T reset reportedStepRef here - it causes race conditions with multi-event triggers
                // It gets reset when new guidance arrives
                setIsWaitingForNextStep(false);
                return remaining;
            }
        });
        // Schedule the report OUTSIDE the state updater, with its own guard
        setTimeout(() => {
            // Guard against React Strict Mode double-scheduling
            if (sentSocketMessageRef.current === stepKey) {
                return; // Already sent for this step
            }
            sentSocketMessageRef.current = stepKey;
            reportActionCompleted(step, capturedIsLastStep);
        }, 0);
    }, [reportActionCompleted, cleanupListeners]);
    useEffect(() => {
        if (!editContext?.addSocketMessageListener)
            return;
        const unsubscribe = editContext.addSocketMessageListener((message) => {
            if (message.type === "guidance-step") {
                // Single step - assume final unless more guidance arrives
                const step = message.payload;
                setIsWaitingForNextStep(false);
                setIsFinalSequence(true); // Single steps default to final
                setStepQueue([]);
                setCurrentStep(step);
                setTotalSteps(1);
                lastProcessedStepRef.current = null;
                reportedStepRef.current = null; // Reset for new guidance
                sentSocketMessageRef.current = null; // Reset for new guidance
                setShow(true);
            }
            else if (message.type === "guidance-sequence") {
                // Multiple steps
                const payload = message.payload;
                if (payload.steps && payload.steps.length > 0) {
                    const [firstStep, ...remaining] = payload.steps;
                    setIsWaitingForNextStep(false);
                    setIsFinalSequence(payload.isFinalSequence ?? false);
                    setStepQueue(remaining);
                    setCurrentStep(firstStep ?? null);
                    setTotalSteps(payload.steps.length);
                    lastProcessedStepRef.current = null;
                    reportedStepRef.current = null; // Reset for new guidance
                    sentSocketMessageRef.current = null; // Reset for new guidance
                    setShow(true);
                }
            }
            else if (message.type === "guidance-dismiss") {
                dismissGuidance();
            }
        });
        return () => unsubscribe();
    }, [editContext?.addSocketMessageListener, dismissGuidance]);
    useEffect(() => {
        if (!currentStep || isWaitingForNextStep)
            return;
        // Avoid double processing the same step
        const actionsKey = (currentStep.actions ?? [])
            .map((a) => `${a.type}:${a.element ?? ""}`)
            .join("|");
        const stepKey = `${currentStep.title}-${currentStep.focusElement}-${actionsKey}`;
        if (lastProcessedStepRef.current === stepKey)
            return;
        lastProcessedStepRef.current = stepKey;
        const runStep = async () => {
            setFocusRects([]);
            setPointToRect(null);
            const actions = currentStep.actions ?? [];
            const allHighlightRects = [];
            // Collect all elements to highlight from focusElement and action elements
            const selectorsToHighlight = new Set();
            if (currentStep.focusElement) {
                selectorsToHighlight.add(currentStep.focusElement);
            }
            // Also highlight elements from actions (especially for input, click, etc.)
            actions.forEach((action) => {
                const selector = action.element || currentStep.focusElement;
                if (selector &&
                    ["input", "click", "drag", "drop"].includes(action.type)) {
                    selectorsToHighlight.add(selector);
                }
            });
            // Attempt to highlight all collected selectors
            for (const selector of selectorsToHighlight) {
                const highlightResult = await waitForElements(selector);
                if (highlightResult?.rects?.length) {
                    allHighlightRects.push(...highlightResult.rects);
                }
            }
            if (allHighlightRects.length > 0) {
                setFocusRects(allHighlightRects);
                // Calculate bounding box of ALL highlighted elements for bubble positioning
                if (allHighlightRects.length === 1) {
                    setPointToRect(allHighlightRects[0] ?? null);
                }
                else {
                    // Compute combined bounding box
                    const minLeft = Math.min(...allHighlightRects.map((r) => r.left));
                    const minTop = Math.min(...allHighlightRects.map((r) => r.top));
                    const maxRight = Math.max(...allHighlightRects.map((r) => r.left + r.width));
                    const maxBottom = Math.max(...allHighlightRects.map((r) => r.top + r.height));
                    setPointToRect(new DOMRect(minLeft, minTop, maxRight - minLeft, maxBottom - minTop));
                }
            }
            const attachActionListeners = (action, elements) => {
                elements.forEach((element) => {
                    if (action.type === "click") {
                        const handleClick = () => onActionCompleted(currentStep);
                        element.addEventListener("click", handleClick, {
                            once: true,
                            capture: true,
                        });
                        activeListenersRef.current.push({
                            element,
                            type: "click",
                            handler: handleClick,
                        });
                    }
                    else if (action.type === "input") {
                        // Track whether user has typed anything
                        let hasTyped = false;
                        const checkInput = () => {
                            let value = "";
                            if (element.getAttribute("contenteditable")) {
                                value = element.innerText;
                            }
                            else if (element instanceof HTMLInputElement ||
                                element instanceof HTMLTextAreaElement) {
                                value = element.value;
                            }
                            if (value.length >= 1) {
                                hasTyped = true;
                            }
                        };
                        // Complete the step when user leaves the input (blur) after typing
                        const handleBlur = () => {
                            if (hasTyped) {
                                onActionCompleted(currentStep);
                            }
                        };
                        element.addEventListener("input", checkInput, { capture: true });
                        element.addEventListener("blur", handleBlur, {
                            once: true,
                            capture: true,
                        });
                        activeListenersRef.current.push({
                            element,
                            type: "input",
                            handler: checkInput,
                        });
                        activeListenersRef.current.push({
                            element,
                            type: "blur",
                            handler: handleBlur,
                        });
                    }
                    else if (action.type === "drag") {
                        // Detect drag start via dragstart, mousedown, or touchstart
                        const handleDragStart = () => onActionCompleted(currentStep);
                        element.addEventListener("dragstart", handleDragStart, {
                            once: true,
                            capture: true,
                        });
                        element.addEventListener("mousedown", handleDragStart, {
                            once: true,
                            capture: true,
                        });
                        element.addEventListener("touchstart", handleDragStart, {
                            once: true,
                            capture: true,
                        });
                        activeListenersRef.current.push({
                            element,
                            type: "dragstart",
                            handler: handleDragStart,
                        });
                        activeListenersRef.current.push({
                            element,
                            type: "mousedown",
                            handler: handleDragStart,
                        });
                        activeListenersRef.current.push({
                            element,
                            type: "touchstart",
                            handler: handleDragStart,
                        });
                    }
                    else if (action.type === "drop") {
                        // Detect drop on element (also listen for click as fallback for click-to-place)
                        const handleDrop = () => onActionCompleted(currentStep);
                        element.addEventListener("drop", handleDrop, {
                            once: true,
                            capture: true,
                        });
                        element.addEventListener("click", handleDrop, {
                            once: true,
                            capture: true,
                        });
                        activeListenersRef.current.push({
                            element,
                            type: "drop",
                            handler: handleDrop,
                        });
                        activeListenersRef.current.push({
                            element,
                            type: "click",
                            handler: handleDrop,
                        });
                    }
                });
            };
            // Process actions in parallel (each action can complete the step)
            await Promise.all(actions.map(async (action) => {
                if (action.type === "timeout") {
                    const timeoutId = window.setTimeout(() => onActionCompleted(currentStep), 3000);
                    activeTimeoutsRef.current.push(timeoutId);
                    return;
                }
                if (action.type === "component-selected" ||
                    action.type === "drag-ended") {
                    // Handled by separate context-based effects
                    return;
                }
                const selector = action.element || currentStep.focusElement;
                if (!selector)
                    return;
                const result = await waitForElements(selector);
                if (!result || result.elements.length === 0)
                    return;
                if (action.type === "waitForElement") {
                    onActionCompleted(currentStep);
                    return;
                }
                attachActionListeners(action, result.elements);
            }));
        };
        runStep();
        return () => {
            cleanupListeners();
        };
    }, [
        currentStep,
        isWaitingForNextStep,
        waitForElements,
        onActionCompleted,
        cleanupListeners,
    ]);
    // Track if context-based actions have completed
    const contextActionCompletedRef = useRef(false);
    const wasDraggingRef = useRef(false);
    // Watch for component selection (completes if already selected OR when selection changes)
    useEffect(() => {
        if (!currentStep || isWaitingForNextStep)
            return;
        const hasComponentSelectedAction = (currentStep.actions ?? []).some((a) => a.type === "component-selected");
        if (!hasComponentSelectedAction)
            return;
        // Prevent double completion
        if (contextActionCompletedRef.current)
            return;
        // Complete if ANY component is selected (either already selected or newly selected)
        if (canvasSelection.length > 0) {
            contextActionCompletedRef.current = true;
            onActionCompleted(currentStep);
        }
    }, [currentStep, isWaitingForNextStep, canvasSelection, onActionCompleted]);
    // Watch for drag-ended (completes when dragObject becomes undefined after being set)
    useEffect(() => {
        if (!currentStep || isWaitingForNextStep)
            return;
        const hasDragEndedAction = (currentStep.actions ?? []).some((a) => a.type === "drag-ended");
        if (!hasDragEndedAction)
            return;
        // Prevent double completion
        if (contextActionCompletedRef.current)
            return;
        const isDragging = !!editContext?.dragObject;
        // Track if we were dragging
        if (isDragging) {
            wasDraggingRef.current = true;
        }
        // Complete when drag ends (was dragging, now not dragging)
        if (wasDraggingRef.current && !isDragging) {
            contextActionCompletedRef.current = true;
            onActionCompleted(currentStep);
        }
    }, [
        currentStep,
        isWaitingForNextStep,
        editContext?.dragObject,
        onActionCompleted,
    ]);
    // Reset context action tracking refs when step changes
    useEffect(() => {
        contextActionCompletedRef.current = false;
        wasDraggingRef.current = false;
    }, [currentStep?.title, currentStep?.focusElement]);
    // State for constrained bubble position
    const [bubbleStyle, setBubbleStyle] = useState({
        position: "fixed",
        left: "50%",
        top: "50%",
        transform: "translate(-50%, -50%)",
        zIndex: 1000,
    });
    // Calculate and constrain bubble position within viewport
    const updateBubblePosition = useCallback(() => {
        if (!show || !currentStep || !pointToRect || isWaitingForNextStep) {
            setBubbleStyle({
                position: "fixed",
                left: "50%",
                top: "50%",
                transform: "translate(-50%, -50%)",
                zIndex: 1000,
            });
            return;
        }
        const preferredPosition = currentStep.bubblePosition || "right";
        const padding = 20;
        const viewportPadding = 16;
        // Get actual bubble dimensions if available, otherwise use estimates
        const bubbleWidth = bubbleRef.current?.offsetWidth || 400;
        const bubbleHeight = bubbleRef.current?.offsetHeight || 200;
        const viewportWidth = window.innerWidth;
        const viewportHeight = window.innerHeight;
        // Helper to check if a position would fit within viewport
        const wouldFit = (left, top, width, height, usesTransform = false) => {
            let effectiveLeft = left;
            if (usesTransform) {
                // For centered positions, check if center point allows full width
                effectiveLeft = left - width / 2;
            }
            return (effectiveLeft >= viewportPadding &&
                effectiveLeft + width <= viewportWidth - viewportPadding &&
                top >= viewportPadding &&
                top + height <= viewportHeight - viewportPadding);
        };
        // Try preferred position first, then fallback to alternatives
        const tryPosition = (position) => {
            let left = 0;
            let top = 0;
            let transform = "";
            if (position === "right") {
                left = pointToRect.left + pointToRect.width + padding;
                top = pointToRect.top;
            }
            else if (position === "left") {
                left = pointToRect.left - bubbleWidth - padding;
                top = pointToRect.top;
            }
            else if (position === "bottom") {
                left = pointToRect.left + pointToRect.width / 2;
                top = pointToRect.top + pointToRect.height + padding;
                transform = "translateX(-50%)";
            }
            else if (position === "top") {
                left = pointToRect.left + pointToRect.width / 2;
                top = pointToRect.top - bubbleHeight - padding;
                transform = "translateX(-50%)";
            }
            else if (position === "top-right") {
                left = pointToRect.left + pointToRect.width;
                top = pointToRect.top - bubbleHeight - padding;
            }
            else if (position === "bottom-right") {
                left = pointToRect.left + pointToRect.width + padding;
                top = pointToRect.top + pointToRect.height + padding;
            }
            else if (position === "top-left") {
                left = pointToRect.left - bubbleWidth - padding;
                top = pointToRect.top - bubbleHeight - padding;
            }
            const fits = wouldFit(left, top, bubbleWidth, bubbleHeight, !!transform);
            return { left, top, transform, fits };
        };
        // Try preferred position first
        let result = tryPosition(preferredPosition);
        // If preferred position doesn't fit well, try alternatives
        if (!result.fits) {
            const alternatives = [];
            const elementCenterX = pointToRect.left + pointToRect.width / 2;
            const isOnLeftSide = elementCenterX < viewportWidth / 3;
            const isOnRightSide = elementCenterX > (viewportWidth * 2) / 3;
            // Generate alternatives based on preferred position and element location
            if (preferredPosition === "right") {
                // If element is on left side (like in a sidebar), prefer bottom/top over left
                if (isOnLeftSide) {
                    alternatives.push("bottom", "top", "left");
                }
                else {
                    alternatives.push("left", "bottom", "top");
                }
            }
            else if (preferredPosition === "left") {
                // If element is on right side, prefer bottom/top over right
                if (isOnRightSide) {
                    alternatives.push("bottom", "top", "right");
                }
                else {
                    alternatives.push("right", "bottom", "top");
                }
            }
            else if (preferredPosition === "bottom") {
                alternatives.push("top", "right", "left");
            }
            else if (preferredPosition === "top") {
                alternatives.push("bottom", "right", "left");
            }
            else {
                // For corner positions, try common alternatives
                alternatives.push("bottom", "top", "right", "left");
            }
            // Try each alternative until we find one that fits
            for (const alt of alternatives) {
                const altResult = tryPosition(alt);
                if (altResult.fits) {
                    result = altResult;
                    break;
                }
                // If none fit perfectly, prefer the one that stays closest to the element
                const currentDistance = Math.abs(result.left - elementCenterX) +
                    Math.abs(result.top - (pointToRect.top + pointToRect.height / 2));
                const altDistance = Math.abs(altResult.left - elementCenterX) +
                    Math.abs(altResult.top - (pointToRect.top + pointToRect.height / 2));
                if (altDistance < currentDistance) {
                    result = altResult;
                }
            }
        }
        let { left, top, transform } = result;
        // Constrain to viewport bounds (final safety check)
        if (transform === "translateX(-50%)") {
            // For centered positions, ensure center point keeps bubble within bounds
            if (left - bubbleWidth / 2 < viewportPadding) {
                left = viewportPadding + bubbleWidth / 2;
            }
            else if (left + bubbleWidth / 2 > viewportWidth - viewportPadding) {
                left = viewportWidth - viewportPadding - bubbleWidth / 2;
            }
        }
        else {
            // For edge-aligned positions
            if (left < viewportPadding) {
                left = viewportPadding;
            }
            else if (left + bubbleWidth > viewportWidth - viewportPadding) {
                left = viewportWidth - bubbleWidth - viewportPadding;
            }
        }
        // Constrain vertical position
        if (top < viewportPadding) {
            top = viewportPadding;
        }
        else if (top + bubbleHeight > viewportHeight - viewportPadding) {
            top = viewportHeight - bubbleHeight - viewportPadding;
        }
        setBubbleStyle({
            position: "fixed",
            left: `${left}px`,
            top: `${top}px`,
            transform: transform || undefined,
            zIndex: 1000,
        });
    }, [show, currentStep, pointToRect, isWaitingForNextStep]);
    useEffect(() => {
        updateBubblePosition();
    }, [updateBubblePosition]);
    // Update position when bubble size changes or window resizes
    useEffect(() => {
        if (!show || !currentStep)
            return;
        const resizeObserver = new ResizeObserver(() => {
            updateBubblePosition();
        });
        if (bubbleRef.current) {
            resizeObserver.observe(bubbleRef.current);
        }
        const handleResize = () => {
            updateBubblePosition();
        };
        window.addEventListener("resize", handleResize);
        window.addEventListener("scroll", handleResize, true);
        return () => {
            resizeObserver.disconnect();
            window.removeEventListener("resize", handleResize);
            window.removeEventListener("scroll", handleResize, true);
        };
    }, [show, currentStep, updateBubblePosition]);
    if (!show || !currentStep)
        return null;
    // For multiple spotlights, we use SVG mask to create cutouts
    const hasSpotlights = focusRects.length > 0 && !isWaitingForNextStep;
    const showProgress = totalSteps > 1;
    return (_jsxs(_Fragment, { children: [hasSpotlights && (_jsxs("svg", { style: {
                    position: "fixed",
                    top: 0,
                    left: 0,
                    width: "100%",
                    height: "100%",
                    pointerEvents: "none",
                    zIndex: 998,
                }, children: [_jsx("defs", { children: _jsxs("mask", { id: "spotlight-mask", children: [_jsx("rect", { x: "0", y: "0", width: "100%", height: "100%", fill: "white" }), focusRects.map((rect, index) => (_jsx("rect", { x: rect.left, y: rect.top, width: rect.width, height: rect.height, rx: "12", ry: "12", fill: "black" }, index)))] }) }), _jsx("rect", { x: "0", y: "0", width: "100%", height: "100%", fill: "color-mix(in srgb, var(--color-neutral-grey-100) 40%, transparent)", mask: "url(#spotlight-mask)" })] })), hasSpotlights &&
                focusRects.map((rect, index) => (_jsx("div", { style: {
                        position: "fixed",
                        left: rect.left,
                        top: rect.top,
                        width: rect.width,
                        height: rect.height,
                        borderRadius: "12px",
                        boxShadow: "0 0 0 2px color-mix(in srgb, var(--color-highlight-100) 60%, transparent), 0 0 12px color-mix(in srgb, var(--color-highlight-100) 40%, transparent)",
                        pointerEvents: "none",
                        zIndex: 999,
                        transition: "all 0.3s ease-in-out",
                    } }, index))), isWaitingForNextStep && (_jsx("div", { style: {
                    position: "fixed",
                    top: 0,
                    left: 0,
                    width: "100%",
                    height: "100%",
                    backgroundColor: "color-mix(in srgb, var(--color-neutral-grey-100) 30%, transparent)",
                    pointerEvents: "none",
                    zIndex: 997,
                } })), _jsx("div", { ref: bubbleRef, style: bubbleStyle, className: "fade-in animate-in slide-in-from-top-2 duration-300", children: _jsxs("div", { className: "border-highlight-100 relative flex max-w-sm flex-col gap-3 rounded-xl border bg-white p-5 shadow-2xl", children: [_jsx(CloseButton, { onClick: dismissGuidance, label: "Dismiss guidance", className: "absolute top-3 right-3" }), _jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Logo, { className: "h-6 w-6" }), _jsx("h3", { className: "text-highlight-100 text-lg font-bold", children: isWaitingForNextStep ? "Great job!" : currentStep.title })] }), showProgress &&
                            !isWaitingForNextStep &&
                            (() => {
                                const currentStepNum = totalSteps - stepQueue.length;
                                return (_jsxs("div", { className: "flex w-full items-center gap-2", children: [_jsx("div", { className: "flex min-w-0 flex-1 gap-1", "aria-hidden": "true", children: Array.from({ length: totalSteps }).map((_, i) => (_jsx("div", { className: `h-1.5 min-w-[4px] flex-1 rounded-full transition-colors ${i < currentStepNum
                                                    ? "bg-highlight-100"
                                                    : "bg-neutral-grey-10"}` }, i))) }), _jsxs("span", { className: "text-neutral-grey-50 shrink-0 text-xs whitespace-nowrap", children: ["Step ", currentStepNum, " of ", totalSteps] })] }));
                            })(), _jsx("p", { className: "text-neutral-grey-100 text-sm leading-relaxed", children: isWaitingForNextStep
                                ? "Loading next instruction..."
                                : parseBoldFormatting(currentStep.description) }), isWaitingForNextStep ? (_jsxs("div", { className: "mt-2 flex items-center gap-2", children: [_jsx(Loader2, { strokeWidth: 1, className: "text-highlight-100 h-4 w-4 animate-spin" }), _jsx("span", { className: "text-highlight-100 text-xs font-medium tracking-wider", children: "Preparing next step..." })] })) : ((currentStep.actions ?? []).some((a) => !["waitForElement", "timeout"].includes(a.type)) && (_jsxs("div", { className: "mt-2 flex items-center gap-2", children: [_jsx("div", { className: "bg-highlight-100 h-2 w-2 animate-pulse rounded-full" }), _jsx("span", { className: "text-highlight-100 text-xs font-medium tracking-wider", children: stepQueue.length > 0
                                        ? `Waiting... (${stepQueue.length} more steps queued)`
                                        : "Waiting for your action..." })] })))] }) }), _jsx("style", { children: `
          .fade-in {
            animation: fadeIn 0.4s ease-out;
          }
          @keyframes fadeIn {
            from { opacity: 0; transform: translate(-50%, -40%); }
            to { opacity: 1; transform: translate(-50%, -50%); }
          }
          [style*="position: absolute"].fade-in {
            animation: fadeInAbs 0.4s ease-out;
          }
          @keyframes fadeInAbs {
            from { opacity: 0; }
            to { opacity: 1; }
          }
        ` })] }));
}
//# sourceMappingURL=GuidanceOverlay.js.map