import { jsx } from 'react/jsx-runtime';
import React__default, { useRef, useState, useCallback, useEffect } from 'react';
import { motion, AnimatePresence } from 'motion/react';
import { cn } from '../../../utils/cn.js';
import { getMasonryTheme } from './Masonry.theme.js';

const MASONRY_ITEM_INITIAL = { opacity: 0, y: 10 };
const MASONRY_ITEM_ANIMATE = { opacity: 1, y: 0 };
const MASONRY_ITEM_EXIT = { opacity: 0, scale: 0.95 };
const MASONRY_ITEM_TRANSITION_BASE = {
    duration: 0.4,
    ease: [0.2, 0.65, 0.3, 0.9],
};
const Masonry = React__default.forwardRef(({ children, columns = 3, gap = 16, gapBreakpoints, className, style, minColumns = 1, maxColumns = 10, sequential = false, themeOverrides, loading = false, ...props }, ref) => {
    const containerRef = useRef(null);
    const [currentColumns, setCurrentColumns] = useState(typeof columns === 'number' ? columns : 3);
    const [currentGap, setCurrentGap] = useState(gap);
    const [containerWidth, setContainerWidth] = useState(0);
    // Get the theme with any overrides applied
    const theme = getMasonryTheme(themeOverrides);
    // Calculate responsive columns and gap
    const calculateLayout = useCallback(() => {
        if (!containerRef.current)
            return;
        // Use getBoundingClientRect for more accurate width measurement
        const containerRect = containerRef.current.getBoundingClientRect();
        const width = Math.floor(containerRect.width);
        // Only update if width actually changed to prevent unnecessary re-renders
        if (width !== containerWidth && width > 0) {
            setContainerWidth(width);
        }
        // Handle responsive columns
        if (typeof columns === 'object') {
            const breakpoints = Object.keys(columns)
                .map(Number)
                .sort((a, b) => a - b);
            let newColumns = 3; // Default fallback
            for (const breakpoint of breakpoints) {
                if (width >= breakpoint) {
                    newColumns = columns[breakpoint];
                }
            }
            // Ensure columns are within min/max bounds
            newColumns = Math.min(Math.max(newColumns, minColumns), maxColumns);
            setCurrentColumns(newColumns);
        }
        else {
            const clampedColumns = Math.min(Math.max(columns, minColumns), maxColumns);
            setCurrentColumns(clampedColumns);
        }
        // Handle responsive gap
        if (gapBreakpoints) {
            const gapBreakpointKeys = Object.keys(gapBreakpoints)
                .map(Number)
                .sort((a, b) => a - b);
            let newGap = gap;
            for (const breakpoint of gapBreakpointKeys) {
                if (width >= breakpoint) {
                    newGap = gapBreakpoints[breakpoint];
                }
            }
            setCurrentGap(newGap);
        }
        else {
            setCurrentGap(gap);
        }
    }, [columns, gap, gapBreakpoints, minColumns, maxColumns, containerWidth]);
    // Arrange items in masonry layout
    const arrangeItems = useCallback(() => {
        if (!containerRef.current)
            return;
        const container = containerRef.current;
        const wrappers = Array.from(container.children);
        if (wrappers.length === 0)
            return;
        // Get fresh container width to avoid stale closure issues
        const currentContainerWidth = container.offsetWidth;
        // Calculate column width
        const availableWidth = currentContainerWidth - (currentGap * (currentColumns - 1));
        const columnWidth = Math.floor(availableWidth / currentColumns);
        if (columnWidth <= 0)
            return; // Prevent invalid layouts
        // Initialize column heights
        const columnHeights = new Array(currentColumns).fill(0);
        // PHASE 1: READ - Measure all wrapper heights first (prevents layout thrashing)
        const wrapperHeights = [];
        wrappers.forEach((wrapper) => {
            // Temporarily reset positioning to measure natural height
            const originalPosition = wrapper.style.position;
            const originalLeft = wrapper.style.left;
            const originalTop = wrapper.style.top;
            // Set width and reset positioning for accurate measurement
            wrapper.style.width = `${columnWidth}px`;
            wrapper.style.position = 'static';
            wrapper.style.left = '';
            wrapper.style.top = '';
            // Force layout recalculation to get accurate height
            void wrapper.offsetHeight;
            const height = wrapper.offsetHeight;
            wrapperHeights.push(height > 0 ? height : 0);
            // Restore original positioning (will be overwritten in write phase, but keeps state consistent)
            wrapper.style.position = originalPosition;
            wrapper.style.left = originalLeft;
            wrapper.style.top = originalTop;
        });
        // PHASE 2: WRITE - Position all wrappers (batch all DOM writes)
        wrappers.forEach((wrapper, index) => {
            // Set positioning styles
            wrapper.style.position = 'absolute';
            wrapper.style.width = `${columnWidth}px`;
            let targetColumn;
            if (sequential) {
                // Sequential placement: round-robin distribution
                targetColumn = index % currentColumns;
            }
            else {
                // Optimal placement: shortest column
                targetColumn = columnHeights.indexOf(Math.min(...columnHeights));
            }
            // Calculate position
            const left = targetColumn * (columnWidth + currentGap);
            const top = columnHeights[targetColumn];
            // Position the wrapper
            wrapper.style.left = `${left}px`;
            wrapper.style.top = `${top}px`;
            // Update column height using pre-measured height
            const itemHeight = wrapperHeights[index];
            if (itemHeight > 0) {
                columnHeights[targetColumn] = top + itemHeight + currentGap;
            }
        });
        // Set final container height after all items are positioned
        requestAnimationFrame(() => {
            const maxHeight = Math.max(...columnHeights) - currentGap;
            container.style.height = `${Math.max(0, maxHeight)}px`;
        });
    }, [currentColumns, currentGap, sequential]);
    // Handle container resize with ResizeObserver for better performance
    useEffect(() => {
        if (!containerRef.current)
            return;
        let resizeTimeoutId;
        const resizeObserver = new ResizeObserver((_entries) => {
            // Debounce resize events to prevent excessive recalculations
            clearTimeout(resizeTimeoutId);
            resizeTimeoutId = setTimeout(() => {
                calculateLayout();
            }, 100);
        });
        // Fallback window resize handler for older browsers
        const handleWindowResize = () => {
            clearTimeout(resizeTimeoutId);
            resizeTimeoutId = setTimeout(() => {
                calculateLayout();
            }, 100);
        };
        resizeObserver.observe(containerRef.current);
        window.addEventListener('resize', handleWindowResize);
        return () => {
            resizeObserver.disconnect();
            window.removeEventListener('resize', handleWindowResize);
            clearTimeout(resizeTimeoutId);
        };
    }, [calculateLayout]);
    // Initial layout calculation and arrangement
    useEffect(() => {
        // Wait for next tick to ensure container is mounted and measured
        const timer = setTimeout(() => {
            calculateLayout();
            // Only arrange items if we have a valid container width
            if (containerWidth > 0) {
                arrangeItems();
            }
        }, 0);
        return () => clearTimeout(timer);
    }, [calculateLayout, arrangeItems, children, containerWidth]);
    // Re-arrange when children change (with debouncing)
    useEffect(() => {
        let timeoutId;
        const observer = new MutationObserver(() => {
            // Debounce multiple rapid changes
            clearTimeout(timeoutId);
            timeoutId = setTimeout(() => {
                arrangeItems();
            }, 10);
        });
        if (containerRef.current) {
            observer.observe(containerRef.current, {
                childList: true,
                subtree: false, // Only watch direct children, not nested changes
            });
        }
        return () => {
            observer.disconnect();
            clearTimeout(timeoutId);
        };
    }, [arrangeItems]);
    // Generate container classes
    const containerClasses = cn(theme.container.base, loading && theme.container.loading, className);
    // Wrap each child in a positioning div with theme styles
    const wrappedChildren = React__default.Children.map(children, (child, index) => {
        if (!React__default.isValidElement(child)) {
            return child;
        }
        const wrapperClasses = cn(theme.item.base, theme.animations.transition);
        return (jsx(motion.div, { className: wrapperClasses, "data-masonry-item": true, initial: MASONRY_ITEM_INITIAL, animate: MASONRY_ITEM_ANIMATE, exit: MASONRY_ITEM_EXIT, transition: {
                ...MASONRY_ITEM_TRANSITION_BASE,
                delay: index * 0.05,
            }, children: child }, child.key || index));
    });
    return (jsx("div", { ref: (node) => {
            containerRef.current = node;
            if (typeof ref === 'function') {
                ref(node);
            }
            else if (ref) {
                ref.current = node;
            }
        }, className: containerClasses, style: {
            ...style,
            // Ensure container has minimum dimensions for proper layout calculation
            minHeight: loading ? '200px' : undefined,
        }, ...props, children: jsx(AnimatePresence, { mode: "popLayout", children: wrappedChildren }) }));
});
Masonry.displayName = 'Masonry';

export { Masonry };
