import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
import * as React from 'react';
import { useMotionValue, AnimatePresence, motion, useReducedMotion, useSpring } from 'motion/react';
import { PaperAirplaneIcon } from '@heroicons/react/20/solid';
import { cn } from '../../../utils/cn.js';
import { Avatar } from '../../core/Avatar/Avatar.js';
import { customCursorVariants, reducedMotionSpringConfig, defaultSpringConfig } from './CustomCursor.animations.js';
import { customCursorTheme } from './CustomCursor.theme.js';

/**
 * Context for CustomCursor state management
 */
const CustomCursorContext = React.createContext(undefined);
/**
 * Hook to access CustomCursor context
 * @throws Error if used outside CustomCursorProvider
 */
const useCustomCursor = () => {
    const context = React.useContext(CustomCursorContext);
    if (!context) {
        throw new Error("useCustomCursor must be used within a CustomCursorProvider");
    }
    return context;
};
/**
 * CustomCursorProvider - Context provider component
 * Tracks mouse movement within parent element and manages cursor state
 *
 * @example
 * ```tsx
 * <CustomCursorProvider>
 *   <CustomCursor>
 *     <div className="w-4 h-4 bg-blue-500 rounded-full" />
 *   </CustomCursor>
 *   <YourContent />
 * </CustomCursorProvider>
 * ```
 */
const CustomCursorProvider = React.forwardRef(({ children, className, allowedPointerTypes = ["mouse"], ...props }, ref) => {
    const [cursorPos, setCursorPos] = React.useState({ x: 0, y: 0 });
    const [isActive, setIsActive] = React.useState(false);
    const containerRef = React.useRef(null);
    const cursorRef = React.useRef(null);
    const allowedPointerTypesSet = React.useMemo(() => new Set(allowedPointerTypes), [allowedPointerTypes]);
    React.useImperativeHandle(ref, () => containerRef.current);
    React.useEffect(() => {
        if (!containerRef.current)
            return;
        const parent = containerRef.current.parentElement;
        if (!parent)
            return;
        // Ensure parent has positioning context for absolute children
        const computedPosition = getComputedStyle(parent).position;
        const previousInlinePosition = parent.style.position;
        const shouldOverridePosition = computedPosition === "static";
        if (shouldOverridePosition) {
            parent.style.position = "relative";
        }
        const handlePointerMove = (event) => {
            const pointerType = (event.pointerType ||
                "mouse");
            if (!allowedPointerTypesSet.has(pointerType))
                return;
            const rect = parent.getBoundingClientRect();
            // Convert viewport coordinates to parent-relative coordinates
            setCursorPos({
                x: event.clientX - rect.left,
                y: event.clientY - rect.top,
            });
            setIsActive(true);
        };
        const handlePointerLeave = (event) => {
            const pointerType = (event.pointerType ||
                "mouse");
            if (!allowedPointerTypesSet.has(pointerType))
                return;
            setIsActive(false);
        };
        const handleMouseMove = (event) => {
            const rect = parent.getBoundingClientRect();
            setCursorPos({
                x: event.clientX - rect.left,
                y: event.clientY - rect.top,
            });
            setIsActive(true);
        };
        const handleMouseLeave = () => {
            setIsActive(false);
        };
        const supportsPointerEvents = typeof window !== "undefined" && "PointerEvent" in window;
        if (supportsPointerEvents) {
            parent.addEventListener("pointermove", handlePointerMove);
            parent.addEventListener("pointerleave", handlePointerLeave);
        }
        else {
            parent.addEventListener("mousemove", handleMouseMove);
            parent.addEventListener("mouseleave", handleMouseLeave);
        }
        return () => {
            if (supportsPointerEvents) {
                parent.removeEventListener("pointermove", handlePointerMove);
                parent.removeEventListener("pointerleave", handlePointerLeave);
            }
            else {
                parent.removeEventListener("mousemove", handleMouseMove);
                parent.removeEventListener("mouseleave", handleMouseLeave);
            }
            if (shouldOverridePosition) {
                parent.style.position = previousInlinePosition;
            }
        };
    }, [allowedPointerTypesSet]);
    const contextValue = React.useMemo(() => ({ cursorPos, isActive, containerRef, cursorRef }), [cursorPos, isActive]);
    return (jsx(CustomCursorContext.Provider, { value: contextValue, children: jsx("div", { ref: containerRef, "data-slot": "custom-cursor-provider", className: className, ...props, children: children }) }));
});
CustomCursorProvider.displayName = "CustomCursorProvider";
/**
 * CustomCursor - Main cursor element component
 * Follows mouse position exactly with instant movement
 *
 * @example
 * ```tsx
 * <CustomCursor size="md" color="primary" variant="glass">
 *   <PaperAirplaneIcon />
 * </CustomCursor>
 * ```
 */
const CustomCursor = React.forwardRef(({ children, size = "md", color = "primary", variant = "default", shape = "arrow", className, style, onAnimationStart: _onAnimationStart, onAnimationEnd: _onAnimationEnd, onDrag: _onDrag, onDragStart: _onDragStart, onDragEnd: _onDragEnd, ...props }, ref) => {
    const { cursorPos, isActive, containerRef, cursorRef } = useCustomCursor();
    React.useImperativeHandle(ref, () => cursorRef.current);
    const x = useMotionValue(0);
    const y = useMotionValue(0);
    // Hide default cursor when custom cursor is active
    React.useEffect(() => {
        var _a;
        const parentElement = (_a = containerRef.current) === null || _a === void 0 ? void 0 : _a.parentElement;
        if (!parentElement || !isActive) {
            return;
        }
        const previousInlineCursor = parentElement.style.cursor;
        parentElement.style.cursor = "none";
        return () => {
            parentElement.style.cursor = previousInlineCursor;
        };
    }, [containerRef, isActive]);
    // Update cursor position instantly (no spring)
    React.useEffect(() => {
        x.set(cursorPos.x);
        y.set(cursorPos.y);
    }, [cursorPos, x, y]);
    // Default content based on shape
    const defaultContent = shape === "arrow" ? (jsx(PaperAirplaneIcon, { className: "w-full h-full rotate-180" })) : (jsx("div", { className: "w-full h-full" }));
    return (jsx(AnimatePresence, { children: isActive && (jsx(motion.div, { ref: cursorRef, "data-slot": "custom-cursor", className: cn(customCursorTheme.baseStyle, customCursorTheme.cursorStyle, customCursorTheme.sizes[size], customCursorTheme.colors[color], customCursorTheme.variants[variant], customCursorTheme.shapes[shape], className), style: { top: y, left: x, ...style }, variants: customCursorVariants, initial: "initial", animate: "animate", exit: "exit", role: "presentation", "aria-hidden": "true", ...props, children: children || defaultContent })) }));
});
CustomCursor.displayName = "CustomCursor";
/**
 * Calculate offset based on alignment and element dimensions
 */
const calculateOffset = (align, sideOffset, width, height) => {
    switch (align) {
        case "center":
            return { x: width / 2, y: height / 2 };
        case "top":
            return { x: width / 2, y: height + sideOffset };
        case "top-left":
            return { x: width + sideOffset, y: height + sideOffset };
        case "top-right":
            return { x: -sideOffset, y: height + sideOffset };
        case "bottom":
            return { x: width / 2, y: -sideOffset };
        case "bottom-left":
            return { x: width + sideOffset, y: -sideOffset };
        case "bottom-right":
            return { x: -sideOffset, y: -sideOffset };
        case "left":
            return { x: width + sideOffset, y: height / 2 };
        case "right":
            return { x: -sideOffset, y: height / 2 };
        default:
            return { x: 0, y: 0 };
    }
};
/**
 * CustomCursorFollow - Smooth following cursor element
 * Follows the main cursor with spring physics for smooth, laggy movement
 *
 * @example
 * ```tsx
 * <CustomCursorFollow align="bottom-right" sideOffset={20}>
 *   <div className="bg-white rounded-lg shadow-lg p-2">
 *     Tooltip content
 *   </div>
 * </CustomCursorFollow>
 * ```
 */
/** Available colors for random selection in user variant */
const userVariantColors = [
    "primary",
    "success",
    "warning",
    "danger",
];
const CustomCursorFollow = React.forwardRef(({ children, sideOffset = 15, align = "bottom-right", transition, variant = "default", avatar, name, color, className, style, onAnimationStart: _onAnimationStart2, onAnimationEnd: _onAnimationEnd2, onDrag: _onDrag2, onDragStart: _onDragStart2, onDragEnd: _onDragEnd2, ...props }, ref) => {
    const { cursorPos, isActive, cursorRef } = useCustomCursor();
    const cursorFollowRef = React.useRef(null);
    const shouldReduceMotion = useReducedMotion();
    // Select random color for user variant if not provided (stable across renders)
    const resolvedColor = React.useMemo(() => {
        if (color)
            return color;
        const randomIndex = Math.floor(Math.random() * userVariantColors.length);
        return userVariantColors[randomIndex];
    }, [color]);
    React.useImperativeHandle(ref, () => cursorFollowRef.current);
    const x = useMotionValue(0);
    const y = useMotionValue(0);
    const [followDimensions, setFollowDimensions] = React.useState({
        width: 0,
        height: 0,
    });
    const [cursorDimensions, setCursorDimensions] = React.useState({
        width: 20,
        height: 20,
    });
    // Apply spring physics for smooth following
    const springConfig = shouldReduceMotion
        ? reducedMotionSpringConfig
        : (transition !== null && transition !== void 0 ? transition : defaultSpringConfig);
    const springX = useSpring(x, springConfig);
    const springY = useSpring(y, springConfig);
    // Track size changes on the follow element
    React.useEffect(() => {
        if (!isActive)
            return;
        const followElement = cursorFollowRef.current;
        if (!followElement)
            return;
        const updateDimensions = () => {
            var _a, _b;
            const rect = followElement.getBoundingClientRect();
            const width = (_a = rect === null || rect === void 0 ? void 0 : rect.width) !== null && _a !== void 0 ? _a : 0;
            const height = (_b = rect === null || rect === void 0 ? void 0 : rect.height) !== null && _b !== void 0 ? _b : 0;
            setFollowDimensions((prev) => prev.width === width && prev.height === height
                ? prev
                : { width, height });
        };
        updateDimensions();
        const cleanupFns = [];
        if (typeof ResizeObserver !== "undefined") {
            const observer = new ResizeObserver(updateDimensions);
            observer.observe(followElement);
            cleanupFns.push(() => observer.disconnect());
        }
        if (typeof window !== "undefined") {
            window.addEventListener("resize", updateDimensions);
            cleanupFns.push(() => window.removeEventListener("resize", updateDimensions));
        }
        return () => {
            cleanupFns.forEach((cleanup) => cleanup());
        };
    }, [isActive]);
    // Track size changes on the main cursor element
    React.useEffect(() => {
        if (!isActive)
            return;
        const cursorElement = cursorRef.current;
        if (!cursorElement)
            return;
        const updateCursorDimensions = () => {
            var _a, _b;
            const rect = cursorElement.getBoundingClientRect();
            const width = (_a = rect === null || rect === void 0 ? void 0 : rect.width) !== null && _a !== void 0 ? _a : 0;
            const height = (_b = rect === null || rect === void 0 ? void 0 : rect.height) !== null && _b !== void 0 ? _b : 0;
            setCursorDimensions((prev) => prev.width === width && prev.height === height
                ? prev
                : { width, height });
        };
        updateCursorDimensions();
        const cleanupFns = [];
        if (typeof ResizeObserver !== "undefined") {
            const observer = new ResizeObserver(updateCursorDimensions);
            observer.observe(cursorElement);
            cleanupFns.push(() => observer.disconnect());
        }
        if (typeof window !== "undefined") {
            window.addEventListener("resize", updateCursorDimensions);
            cleanupFns.push(() => window.removeEventListener("resize", updateCursorDimensions));
        }
        return () => {
            cleanupFns.forEach((cleanup) => cleanup());
        };
    }, [cursorRef, isActive]);
    const cursorX = cursorPos.x;
    const cursorY = cursorPos.y;
    // Calculate and update position with offset using cached dimensions
    React.useEffect(() => {
        const offset = calculateOffset(align, sideOffset, followDimensions.width, followDimensions.height);
        const cursorWidth = cursorDimensions.width;
        const cursorHeight = cursorDimensions.height;
        x.set(cursorX - offset.x + cursorWidth / 2);
        y.set(cursorY - offset.y + cursorHeight / 2);
    }, [
        align,
        cursorDimensions.height,
        cursorDimensions.width,
        cursorX,
        cursorY,
        followDimensions.height,
        followDimensions.width,
        sideOffset,
        x,
        y,
    ]);
    // Determine content based on variant
    const renderContent = () => {
        if (variant === "user") {
            return (jsxs(Fragment, { children: [avatar && jsx(Avatar, { src: avatar, name: name || "User", size: "xs" }), name && (jsx("span", { className: "text-white text-sm font-medium whitespace-nowrap", children: name }))] }));
        }
        return children;
    };
    return (jsx(AnimatePresence, { children: isActive && (jsx(motion.div, { ref: cursorFollowRef, "data-slot": "custom-cursor-follow", className: cn(customCursorTheme.baseStyle, customCursorTheme.cursorFollowVariants[variant], variant === "user" &&
                customCursorTheme.cursorFollowColors[resolvedColor], className), style: { top: springY, left: springX, ...style }, variants: customCursorVariants, initial: "initial", animate: "animate", exit: "exit", role: "presentation", "aria-hidden": "true", ...props, children: renderContent() })) }));
});
CustomCursorFollow.displayName = "CustomCursorFollow";

export { CustomCursor, CustomCursorFollow, CustomCursorProvider, useCustomCursor };
