/**
 * Optimized Masonry Layout for Dashboard Cards
 * - Minimal observers, throttled updates
 * - Cards maintain column assignment on resize
 * - Only repositions when container width changes significantly
 */
window.cardMasonry = {
    _instances: {},
    _observers: {},
    _throttleTimers: {},
    _lastWidths: {},
    _isLayoutRunning: {},
    _focusedCards: {}, // containerId -> Set of focused cardIds
    _layoutPaused: {}, // containerId -> boolean

    _initDebounce: {},
    _lastInitTime: {},
    _viewportWatchers: {},

    getViewportWidth: function() {
        return window.innerWidth || document.documentElement?.clientWidth || 0;
    },

    registerViewportWatcher: function(containerId, dotNetRef) {
        if (!dotNetRef || !containerId) {
            return;
        }

        this.unregisterViewportWatcher(containerId);

        let resizeTimer = null;
        const notify = () => {
            dotNetRef.invokeMethodAsync('OnViewportWidthChanged', this.getViewportWidth())
                .catch(() => {});
        };

        const onResize = () => {
            if (resizeTimer) {
                clearTimeout(resizeTimer);
            }

            resizeTimer = setTimeout(() => {
                resizeTimer = null;
                notify();
            }, 120);
        };

        window.addEventListener('resize', onResize, { passive: true });

        this._viewportWatchers[containerId] = {
            onResize,
            dotNetRef,
            getTimer: () => resizeTimer,
            clearTimer: () => {
                if (resizeTimer) {
                    clearTimeout(resizeTimer);
                    resizeTimer = null;
                }
            }
        };

        notify();
    },

    unregisterViewportWatcher: function(containerId) {
        const watcher = this._viewportWatchers[containerId];
        if (!watcher) {
            return;
        }

        window.removeEventListener('resize', watcher.onResize);
        watcher.clearTimer();
        delete this._viewportWatchers[containerId];
    },

    /**
     * Initialize masonry layout
     */
    init: function(containerId, columnCount, gap) {
        const container = document.getElementById(containerId);
        if (!container) return false;

        // Debounce rapid init calls (within 500ms)
        const now = Date.now();
        const lastInit = this._lastInitTime[containerId] || 0;
        if (now - lastInit < 500 && this._instances[containerId]) {
            return true; // Already initialized recently
        }
        this._lastInitTime[containerId] = now;

        gap = gap || 6;
        columnCount = Math.max(1, columnCount || 6);

        // Preserve existing card column assignments if re-initializing
        const existingConfig = this._instances[containerId];
        const cardColumns = existingConfig?.cardColumns || {};
        const itemHeights = existingConfig?.itemHeights || {};

        // Store config with card column assignments and last known heights
        this._instances[containerId] = { 
            columnCount, 
            gap, 
            cardColumns,
            itemHeights
        };
        this._lastWidths[containerId] = 0;

        // Disconnect existing observers
        if (this._observers[containerId]) {
            this._observers[containerId].resize?.disconnect();
            this._observers[containerId].items?.disconnect();
            delete this._observers[containerId];
        }

        // Initial layout - debounce to prevent multiple rapid calls
        if (this._initDebounce[containerId]) {
            clearTimeout(this._initDebounce[containerId]);
        }
        this._initDebounce[containerId] = setTimeout(() => {
            this._initDebounce[containerId] = null;
            requestAnimationFrame(() => this._layout(containerId, true));
        }, 50);

        // ResizeObserver for container - only trigger on significant width changes
        const resizeObserver = new ResizeObserver((entries) => {
            const entry = entries[0];
            if (!entry) return;
            
            const newWidth = Math.floor(entry.contentRect.width);
            const lastWidth = this._lastWidths[containerId] || 0;
            
            // Only re-layout if width changed by more than 20px
            if (Math.abs(newWidth - lastWidth) > 20) {
                this._lastWidths[containerId] = newWidth;
                this._throttledLayout(containerId);
            }
        });
        resizeObserver.observe(container);
        
        // ResizeObserver for individual items - detect height changes
        const self = this;
        const itemsObserver = new ResizeObserver((entries) => {
            const config = self._instances[containerId];
            if (!config) return;
            
            let heightChanged = false;
            for (const entry of entries) {
                const item = entry.target;
                const cardId = item.dataset?.cardId;
                if (!cardId) continue;
                
                const newHeight = Math.floor(entry.contentRect.height);
                const oldHeight = config.itemHeights[cardId] || 0;
                
                // Only re-layout if height changed by more than 30px (reduce sensitivity)
                if (Math.abs(newHeight - oldHeight) > 30) {
                    config.itemHeights[cardId] = newHeight;
                    heightChanged = true;
                }
            }
            
            if (heightChanged) {
                self._throttledLayout(containerId);
            }
        });
        
        // Observe all masonry items for height changes
        const items = container.querySelectorAll('.masonry-item');
        items.forEach(item => itemsObserver.observe(item));
        
        this._observers[containerId] = { resize: resizeObserver, items: itemsObserver };

        // Add focusin/focusout listeners to pause layout during editing
        // (reusing 'self' variable declared above for ResizeObserver)
        container.addEventListener('focusin', function(e) {
            const card = e.target.closest('.masonry-item');
            if (card) {
                const cardId = card.dataset.cardId;
                if (cardId) {
                    self.pauseLayout(containerId, cardId);
                }
            }
        });
        
        container.addEventListener('focusout', function(e) {
            const card = e.target.closest('.masonry-item');
            if (card) {
                const cardId = card.dataset.cardId;
                if (cardId) {
                    // Small delay to handle focus moving within the same card
                    setTimeout(() => {
                        // Check if focus is still within this card
                        const activeElement = document.activeElement;
                        const stillInCard = card.contains(activeElement);
                        if (!stillInCard) {
                            self.resumeLayout(containerId, cardId);
                        }
                    }, 100);
                }
            }
        });

        return true;
    },

    _throttledLayout: function(containerId) {
        // Skip layout if paused (card is being edited)
        if (this._layoutPaused[containerId]) return;
        
        // Throttle to max once per 200ms (increased to reduce flickering)
        if (this._throttleTimers[containerId]) {
            // Skip logging to reduce console spam
            return;
        }
        
        this._throttleTimers[containerId] = setTimeout(() => {
            this._throttleTimers[containerId] = null;
            // Check again in case pause happened during throttle
            if (!this._layoutPaused[containerId]) {
                requestAnimationFrame(() => this._layout(containerId, false));
            }
        }, 200);
    },

    /**
     * Pause layout reflow (called when user starts editing a card)
     */
    pauseLayout: function(containerId, cardId) {
        if (!this._focusedCards[containerId]) {
            this._focusedCards[containerId] = new Set();
        }
        this._focusedCards[containerId].add(cardId);
        this._layoutPaused[containerId] = true;
        
        // Set high z-index on the focused card
        const container = document.getElementById(containerId);
        if (container) {
            const card = container.querySelector(`[data-card-id="${cardId}"]`);
            if (card) {
                card.classList.add('masonry-item-focused');
            }
        }
    },

    /**
     * Resume layout reflow (called when user stops editing a card)
     */
    resumeLayout: function(containerId, cardId) {
        if (this._focusedCards[containerId]) {
            this._focusedCards[containerId].delete(cardId);
            
            // Only unpause if no cards are focused
            if (this._focusedCards[containerId].size === 0) {
                this._layoutPaused[containerId] = false;
                // Trigger layout to reposition cards
                this._throttledLayout(containerId);
            }
        }
        
        // Remove high z-index from the card
        const container = document.getElementById(containerId);
        if (container) {
            const card = container.querySelector(`[data-card-id="${cardId}"]`);
            if (card) {
                card.classList.remove('masonry-item-focused');
            }
        }
    },

    _layout: function(containerId, isInitial) {
        // Prevent concurrent layouts
        if (this._isLayoutRunning[containerId]) return;
        this._isLayoutRunning[containerId] = true;

        // Debug logging (disabled to reduce console spam)
        // console.log(`[Masonry] _layout called: containerId=${containerId}, isInitial=${isInitial}`);

        try {
            const config = this._instances[containerId];
            if (!config) return;

            const container = document.getElementById(containerId);
            if (!container) return;

            const items = Array.from(container.querySelectorAll('.masonry-item'));
            if (items.length === 0) {
                container.style.height = '100px';
                return;
            }

            const { columnCount, gap, cardColumns } = config;
            const containerWidth = container.offsetWidth;

            if (columnCount === 1) {
                items.sort((a, b) => {
                    const orderA = parseInt(a.dataset.order) || 0;
                    const orderB = parseInt(b.dataset.order) || 0;
                    return orderA - orderB;
                });

                items.forEach((item) => {
                    item.style.position = 'relative';
                    item.style.left = '';
                    item.style.top = '';
                    item.style.width = '100%';
                    item.style.boxSizing = 'border-box';
                });

                container.style.position = 'relative';
                container.style.height = 'auto';
                return;
            }
            
            if (containerWidth === 0) {
                setTimeout(() => {
                    this._isLayoutRunning[containerId] = false;
                    this._layout(containerId, isInitial);
                }, 100);
                return;
            }

            const colWidth = (containerWidth - (gap * (columnCount - 1))) / columnCount;
            const colHeights = new Array(columnCount).fill(0);

            // Sort items by ORDER first (globally), then by column
            // This allows cards in column 3 to appear ABOVE span-2 cards from column 2
            // if the column 3 card has a lower order number
            items.sort((a, b) => {
                const orderA = parseInt(a.dataset.order) || 0;
                const orderB = parseInt(b.dataset.order) || 0;
                if (orderA !== orderB) return orderA - orderB;
                // Same order: sort by column (lower column first)
                const colA = parseInt(a.dataset.column) || 0;
                const colB = parseInt(b.dataset.column) || 0;
                return colA - colB;
            });
            
            // Position items - use assigned column from data attribute (column-based layout)
            items.forEach((item) => {
                const cardId = item.dataset.cardId;
                const span = Math.min(parseInt(item.dataset.span) || 1, columnCount);
                
                // Use the assigned column from data attribute (column-based, not masonry)
                let assignedCol = parseInt(item.dataset.column) || 0;
                
                // Clamp to valid range
                if (assignedCol < 0) assignedCol = 0;
                if (assignedCol + span > columnCount) {
                    assignedCol = Math.max(0, columnCount - span);
                }
                
                const bestCol = assignedCol;

                // Get current top position for this column
                let top = 0;
                for (let i = 0; i < span; i++) {
                    top = Math.max(top, colHeights[bestCol + i]);
                }

                const left = bestCol * (colWidth + gap);
                const width = (colWidth * span) + (gap * (span - 1));

                // Apply styles
                item.style.position = 'absolute';
                item.style.left = `${left}px`;
                item.style.top = `${top}px`;
                item.style.width = `${width}px`;
                item.style.boxSizing = 'border-box';

                // Measure and update column heights
                const height = item.offsetHeight || 300;
                for (let i = 0; i < span; i++) {
                    colHeights[bestCol + i] = top + height + gap;
                }
            });

            // Set container height
            const maxHeight = Math.max(...colHeights);
            container.style.position = 'relative';
            container.style.height = `${Math.max(100, maxHeight)}px`;
            
        } finally {
            this._isLayoutRunning[containerId] = false;
        }
    },

    refresh: function(containerId) {
        // Force full re-layout
        const config = this._instances[containerId];
        if (config) config.cardColumns = {}; // Clear column assignments
        
        const container = document.getElementById(containerId);
        if (!container) return;
        
        // Reset all item positions first to prevent ghost/duplicate visual artifacts
        const items = container.querySelectorAll('.masonry-item');
        items.forEach(item => {
            // Reset to relative positioning temporarily
            item.style.position = 'relative';
            item.style.left = '';
            item.style.top = '';
            item.style.width = '';
            item.style.transition = 'opacity 0.15s ease';
            item.style.opacity = '0'; // Hide during transition
        });
        
        // Re-observe any new masonry items (Blazor may have recreated DOM)
        if (this._observers[containerId]?.items) {
            items.forEach(item => {
                this._observers[containerId].items.observe(item);
            });
        }
        
        // Layout after a frame to ensure DOM is stable
        requestAnimationFrame(() => {
            requestAnimationFrame(() => {
                this._layout(containerId, true);
                // Show items after layout with fresh query
                container.querySelectorAll('.masonry-item').forEach(item => {
                    item.style.opacity = '1';
                });
            });
        });
    },

    dispose: function(containerId) {
        this.unregisterViewportWatcher(containerId);
        
        // Clean up observers
        if (this._observers[containerId]) {
            this._observers[containerId].resize?.disconnect();
            this._observers[containerId].items?.disconnect();
            delete this._observers[containerId];
        }
        if (this._throttleTimers[containerId]) {
            clearTimeout(this._throttleTimers[containerId]);
            delete this._throttleTimers[containerId];
        }
        
        // Clear all inline styles from items
        const container = document.getElementById(containerId);
        if (container) {
            container.querySelectorAll('.masonry-item').forEach(item => {
                item.style.position = '';
                item.style.left = '';
                item.style.top = '';
                item.style.width = '';
                item.style.opacity = '';
                item.style.transition = '';
            });
        }
        
        // Clean up dotNetRef
        if (this._dotNetRefs[containerId]) {
            delete this._dotNetRefs[containerId];
        }
        
        delete this._instances[containerId];
        delete this._lastWidths[containerId];
        delete this._isLayoutRunning[containerId];
    },

    // Drag and Drop functionality - using pointer events for better compatibility
    _dragState: null,
    _dotNetRefs: {},
    _dragClone: null,
    _dragHandlers: {}, // Store handlers for cleanup
    _heightResizeHandlers: {},
    _autoScrollInterval: null, // For auto-scroll during drag
    
    /**
     * Auto-scroll when dragging near top/bottom of viewport
     */
    _startAutoScroll: function(e) {
        const self = this;
        const scrollZone = 80; // Pixels from edge to trigger scroll
        const scrollSpeed = 8; // Pixels per frame
        
        // Clear any existing interval
        if (self._autoScrollInterval) {
            cancelAnimationFrame(self._autoScrollInterval);
            self._autoScrollInterval = null;
        }
        
        const doScroll = () => {
            if (!self._dragState) return;
            
            const y = self._lastDragY;
            const viewportHeight = window.innerHeight;
            
            if (y < scrollZone) {
                // Near top - scroll up
                const intensity = 1 - (y / scrollZone);
                window.scrollBy(0, -scrollSpeed * intensity);
            } else if (y > viewportHeight - scrollZone) {
                // Near bottom - scroll down
                const intensity = 1 - ((viewportHeight - y) / scrollZone);
                window.scrollBy(0, scrollSpeed * intensity);
            }
            
            if (self._dragState) {
                self._autoScrollInterval = requestAnimationFrame(doScroll);
            }
        };
        
        self._autoScrollInterval = requestAnimationFrame(doScroll);
    },
    
    _stopAutoScroll: function() {
        if (this._autoScrollInterval) {
            cancelAnimationFrame(this._autoScrollInterval);
            this._autoScrollInterval = null;
        }
    },
    
    _lastDragY: 0, // Track last drag Y position for auto-scroll

    /**
     * Initialize drag and drop for cards using pointer events (more reliable than HTML5 DnD)
     * Uses polling to wait for drag handles to appear in the DOM (more stable than Task.Delay)
     */
    initDragDrop: function(containerId, dotNetRef) {
        const container = document.getElementById(containerId);
        if (!container) {
            return false;
        }

        const self = this;
        self._dotNetRefs[containerId] = dotNetRef;
        
        // Wait for drag handles to appear in DOM (more stable than fixed delays)
        const maxAttempts = 20; // 20 * 50ms = 1 second max wait
        let attempts = 0;
        
        const tryInit = () => {
            const handles = container.querySelectorAll('.drag-handle');
            
            if (handles.length === 0 && attempts < maxAttempts) {
                attempts++;
                requestAnimationFrame(tryInit);
                return;
            }
            
            if (handles.length === 0) {
                return true;
            }
            
            self._initDragDropHandles(containerId, container, handles);
        };
        
        tryInit();
        return true;
    },
    
    /**
     * Internal: Actually attach drag handlers to handles
     */
    _initDragDropHandles: function(containerId, container, handles) {
        const self = this;
        
        // Clean up old handlers first to prevent duplicates
        if (self._dragHandlers[containerId]) {
            self._dragHandlers[containerId].forEach(({ handle, handlers }) => {
                handle.removeEventListener('pointerdown', handlers.pointerdown);
                handle.removeEventListener('pointermove', handlers.pointermove);
                handle.removeEventListener('pointerup', handlers.pointerup);
                handle.removeEventListener('pointercancel', handlers.pointercancel);
            });
        }
        self._dragHandlers[containerId] = [];
        
        
        handles.forEach(handle => {
            const item = handle.closest('.masonry-item');
            if (!item) return;
            
            const cardId = item.dataset.cardId;
            if (!cardId) return;
            
            // Skip if already initialized to prevent duplicate listeners
            if (handle.dataset.dragInit === 'true') return;
            handle.dataset.dragInit = 'true';
            
            // Remove draggable attribute - we'll use pointer events instead
            handle.removeAttribute('draggable');
            
            let isDragging = false;
            let startX, startY;
            let offsetX, offsetY;
            
            handle.addEventListener('pointerdown', function(e) {
                if (e.button !== 0) return; // Only left mouse button
                
                e.preventDefault();
                handle.setPointerCapture(e.pointerId);
                
                isDragging = true;
                startX = e.clientX;
                startY = e.clientY;
                
                const itemRect = item.getBoundingClientRect();
                offsetX = e.clientX - itemRect.left;
                offsetY = e.clientY - itemRect.top;
                
                // Create drag clone
                self._dragClone = item.cloneNode(true);
                self._dragClone.style.position = 'fixed';
                self._dragClone.style.width = itemRect.width + 'px';
                self._dragClone.style.height = itemRect.height + 'px';
                self._dragClone.style.left = (e.clientX - offsetX) + 'px';
                self._dragClone.style.top = (e.clientY - offsetY) + 'px';
                self._dragClone.style.zIndex = '9999';
                self._dragClone.style.opacity = '0.8';
                self._dragClone.style.pointerEvents = 'none';
                self._dragClone.style.transform = 'none';
                self._dragClone.classList.add('drag-clone');
                document.body.appendChild(self._dragClone);
                
                item.classList.add('dragging');
                self._dragState = { cardId, sourceItem: item, containerId };
                
                // Start auto-scroll
                self._lastDragY = e.clientY;
                self._startAutoScroll();
                
            });
            
            handle.addEventListener('pointermove', function(e) {
                if (!isDragging || !self._dragClone) return;
                
                // Track Y position for auto-scroll
                self._lastDragY = e.clientY;
                
                // Move the clone
                self._dragClone.style.left = (e.clientX - offsetX) + 'px';
                self._dragClone.style.top = (e.clientY - offsetY) + 'px';
                
                // Find drop target (card or column)
                const items = container.querySelectorAll('.masonry-item:not(.dragging)');
                let dropTarget = null;
                let insertBefore = false;
                let targetColumn = null;
                let insertAtOrder = -1;
                
                const config = self._instances[containerId];
                const containerRect = container.getBoundingClientRect();
                const colWidth = (containerRect.width - (config.gap * (config.columnCount - 1))) / config.columnCount;
                const relativeX = e.clientX - containerRect.left;
                const cursorColumn = Math.floor(relativeX / (colWidth + config.gap));
                const clampedCursorColumn = Math.max(0, Math.min(cursorColumn, config.columnCount - 1));
                
                // Check if over a card, but ONLY if cursor is in the card's assigned column
                // If cursor is in a column that card spans INTO, treat as column drop
                items.forEach(targetItem => {
                    const rect = targetItem.getBoundingClientRect();
                    const itemCol = parseInt(targetItem.dataset.column) || 0;
                    const padding = 5;
                    
                    if (e.clientX >= rect.left - padding && e.clientX <= rect.right + padding &&
                        e.clientY >= rect.top - padding && e.clientY <= rect.bottom + padding) {
                        // Only use as drop target if cursor is in the card's assigned column
                        if (itemCol === clampedCursorColumn) {
                            dropTarget = targetItem;
                            insertBefore = e.clientY < rect.top + rect.height * 0.4;
                        }
                    }
                });
                
                // If not dropping on a card, do column drop
                if (!dropTarget) {
                    targetColumn = clampedCursorColumn;
                    
                    // Find cards VISUALLY in this column (including span cards from adjacent columns)
                    const cardsVisuallyInCol = Array.from(items).filter(item => {
                        const itemCol = parseInt(item.dataset.column) || 0;
                        const itemSpan = parseInt(item.dataset.span) || 1;
                        return itemCol <= targetColumn && (itemCol + itemSpan) > targetColumn;
                    });
                    
                    // Sort by visual Y position
                    cardsVisuallyInCol.sort((a, b) => a.getBoundingClientRect().top - b.getBoundingClientRect().top);
                    
                    // Find which card cursor is above (check ALL visual cards by Y position)
                    const cursorY = e.clientY;
                    let targetCard = null;
                    let isAboveSpanCard = false;
                    
                    for (let i = 0; i < cardsVisuallyInCol.length; i++) {
                        const cardRect = cardsVisuallyInCol[i].getBoundingClientRect();
                        const cardMidY = cardRect.top + cardRect.height / 2;
                        if (cursorY < cardMidY) {
                            targetCard = cardsVisuallyInCol[i];
                            isAboveSpanCard = true;
                            break;
                        }
                    }
                    
                    // Calculate insertion order for the current column (no redirect)
                    // Cards are always dropped in the column the cursor is in
                    const cardsAssignedToCol = Array.from(items).filter(item => {
                        const itemCol = parseInt(item.dataset.column) || 0;
                        return itemCol === targetColumn;
                    }).sort((a, b) => a.getBoundingClientRect().top - b.getBoundingClientRect().top);
                    
                    if (targetCard) {
                        // Check if targetCard is assigned to this column
                        const targetCardCol = parseInt(targetCard.dataset.column) || 0;
                        if (targetCardCol === targetColumn) {
                            // Target card is in this column - insert before it
                            const targetIdx = cardsAssignedToCol.indexOf(targetCard);
                            insertAtOrder = targetIdx >= 0 ? targetIdx : cardsAssignedToCol.length;
                        } else {
                            // Target card is a span card from another column.
                            // Convert the visual gap above that span card into a position within the
                            // cards actually assigned to the hovered column, so Blazor can keep using
                            // normal per-column ordering.
                            const targetTop = targetCard.getBoundingClientRect().top;
                            const targetIdx = cardsAssignedToCol.findIndex(card =>
                                card.getBoundingClientRect().top >= targetTop - 4
                            );
                            insertAtOrder = targetIdx >= 0 ? targetIdx : cardsAssignedToCol.length;
                            self._dragState.insertBeforeSpanCard = null;
                            self._dragState.spanCardOrder = null;
                        }
                    } else {
                        // Cursor is below all cards - append to end
                        insertAtOrder = cardsAssignedToCol.length;
                    }
                }
                
                // Store for pointerup
                self._dragState.dropTarget = dropTarget;
                self._dragState.insertBefore = insertBefore;
                self._dragState.targetColumn = targetColumn;
                self._dragState.insertAtOrder = insertAtOrder;
                
                // Update visual feedback
                container.querySelectorAll('.masonry-item').forEach(i => {
                    i.classList.remove('drop-above', 'drop-below');
                });
                
                // Remove column highlight
                container.querySelectorAll('.column-drop-indicator').forEach(el => el.remove());
                
                if (dropTarget) {
                    dropTarget.classList.add(insertBefore ? 'drop-above' : 'drop-below');
                } else if (targetColumn !== null) {
                    // Show rectangle preview where card will land (use actual targetColumn which may be redirected)
                    const previewColWidth = colWidth;
                    const left = targetColumn * (colWidth + config.gap);
                    
                    // Find cards in the TARGET column (after potential redirect)
                    const cardsInTargetCol = Array.from(items).filter(item => {
                        const itemCol = parseInt(item.dataset.column) || 0;
                        return itemCol === targetColumn;
                    }).sort((a, b) => a.getBoundingClientRect().top - b.getBoundingClientRect().top);
                    
                    // Calculate Y position based on insertAtOrder
                    let rectY = 0;
                    let rectHeight = 80;
                    
                    if (insertAtOrder < cardsInTargetCol.length) {
                        // Insert before existing card
                        const targetCardRect = cardsInTargetCol[insertAtOrder].getBoundingClientRect();
                        if (insertAtOrder === 0) {
                            rectY = 0;
                        } else {
                            rectY = cardsInTargetCol[insertAtOrder - 1].getBoundingClientRect().bottom - containerRect.top + config.gap;
                        }
                        rectHeight = targetCardRect.top - containerRect.top - rectY - config.gap;
                        if (rectHeight < 40) rectHeight = 60;
                    } else if (cardsInTargetCol.length > 0) {
                        // Append after last card
                        rectY = cardsInTargetCol[cardsInTargetCol.length - 1].getBoundingClientRect().bottom - containerRect.top + config.gap;
                    }
                    
                    // For span cards, use full span width
                    const spanWidth = previewColWidth;
                    
                    // Create rectangle preview
                    const indicator = document.createElement('div');
                    indicator.className = 'column-drop-indicator';
                    indicator.style.cssText = `
                        position: absolute; 
                        left: ${left}px; 
                        top: ${rectY}px; 
                        width: ${spanWidth}px; 
                        height: ${Math.max(rectHeight, 60)}px; 
                        background: rgba(59, 130, 246, 0.15); 
                        border: 2px dashed #3b82f6; 
                        border-radius: 8px; 
                        pointer-events: none;
                    `;
                    container.appendChild(indicator);
                }
            });
            
            handle.addEventListener('pointerup', function(e) {
                if (!isDragging) return;
                
                // Stop auto-scroll
                self._stopAutoScroll();
                
                handle.releasePointerCapture(e.pointerId);
                isDragging = false;
                
                // Get stored drop info from pointermove
                const dropTarget = self._dragState?.dropTarget;
                const insertBefore = self._dragState?.insertBefore;
                const targetColumn = self._dragState?.targetColumn;
                
                // Cleanup
                if (self._dragClone) {
                    self._dragClone.remove();
                    self._dragClone = null;
                }
                
                container.querySelectorAll('.masonry-item').forEach(i => {
                    i.classList.remove('dragging', 'drop-above', 'drop-below');
                });
                container.querySelectorAll('.column-drop-indicator').forEach(el => el.remove());
                
                // Execute drop
                if (self._dragState) {
                    const sourceCardId = self._dragState.cardId;
                    const dotNetRef = self._dotNetRefs[containerId];
                    
                    if (dropTarget) {
                        // Drop on a card
                        const targetCardId = dropTarget.dataset.cardId;
                        if (sourceCardId !== targetCardId && dotNetRef) {
                            dotNetRef.invokeMethodAsync('OnCardReordered', sourceCardId, targetCardId, insertBefore)
                                .then(() => {})
                                .catch(err => {});
                        }
                    } else if (targetColumn !== null) {
                        // Drop in column space - move to specific position in that column
                        const insertAtOrder = self._dragState.insertAtOrder ?? -1;
                        const insertBeforeSpanCard = self._dragState.insertBeforeSpanCard || null;
                        if (dotNetRef) {
                            dotNetRef.invokeMethodAsync('OnCardMovedToColumn', sourceCardId, targetColumn + 1, insertAtOrder, insertBeforeSpanCard)
                                .then(() => {})
                                .catch(err => {});
                        } else {
                        }
                    } else {
                    }
                }
                
                self._dragState = null;
            });
            
            handle.addEventListener('pointercancel', function(e) {
                if (!isDragging) return;
                
                // Stop auto-scroll
                self._stopAutoScroll();
                
                isDragging = false;
                
                if (self._dragClone) {
                    self._dragClone.remove();
                    self._dragClone = null;
                }
                
                container.querySelectorAll('.masonry-item').forEach(i => {
                    i.classList.remove('dragging', 'drop-above', 'drop-below');
                });
                container.querySelectorAll('.column-drop-indicator').forEach(el => el.remove());
                
                self._dragState = null;
            });
        });

        return true;
    },

    initCardHeightResizer: function(handleId, contentContainerId, dotNetRef, minHeight, maxHeight) {
        const handle = document.getElementById(handleId);
        const content = document.getElementById(contentContainerId);
        if (!handle || !content || !dotNetRef) return;

        // Remove prior handlers if already initialized
        this.disposeCardHeightResizer(handleId);

        const minH = Number.isFinite(minHeight) ? minHeight : 180;
        const maxH = Number.isFinite(maxHeight) ? maxHeight : 1200;

        let dragging = false;
        let startY = 0;
        let startHeight = 0;
        let activePointerId = null;

        const onPointerMove = (e) => {
            if (!dragging) return;
            if (activePointerId !== null && e.pointerId !== activePointerId) return;
            const delta = e.clientY - startY;
            const next = Math.max(minH, Math.min(maxH, Math.round(startHeight + delta)));
            content.style.height = `${next}px`;
        };

        const onPointerUp = async (e) => {
            if (!dragging) return;
            if (activePointerId !== null && e && e.pointerId !== undefined && e.pointerId !== activePointerId) return;
            dragging = false;
            if (activePointerId !== null) {
                try {
                    handle.releasePointerCapture?.(activePointerId);
                } catch {
                    // noop
                }
            }
            activePointerId = null;
            document.body.style.userSelect = '';

            const parsed = parseInt((content.style.height || '').replace('px', ''), 10);
            const finalHeight = Number.isFinite(parsed)
                ? Math.max(minH, Math.min(maxH, parsed))
                : Math.max(minH, Math.min(maxH, Math.round(content.clientHeight || startHeight)));

            try {
                await dotNetRef.invokeMethodAsync('OnContentHeightDragEnded', finalHeight);
            } catch {
                // noop
            }
        };

        const onPointerDown = (e) => {
            if (e.button !== 0) return;
            e.preventDefault();
            dragging = true;
            activePointerId = e.pointerId;
            startY = e.clientY;
            startHeight = content.clientHeight;
            document.body.style.userSelect = 'none';
            handle.setPointerCapture?.(e.pointerId);
        };

        handle.addEventListener('pointerdown', onPointerDown);
        window.addEventListener('pointermove', onPointerMove);
        window.addEventListener('pointerup', onPointerUp);
        window.addEventListener('pointercancel', onPointerUp);

        this._heightResizeHandlers[handleId] = {
            handle,
            onPointerDown,
            onPointerMove,
            onPointerUp
        };
    },

    disposeCardHeightResizer: function(handleId) {
        const existing = this._heightResizeHandlers[handleId];
        if (!existing) return;

        existing.handle.removeEventListener('pointerdown', existing.onPointerDown);
        window.removeEventListener('pointermove', existing.onPointerMove);
        window.removeEventListener('pointerup', existing.onPointerUp);
        window.removeEventListener('pointercancel', existing.onPointerUp);

        delete this._heightResizeHandlers[handleId];
    }
};
